Skip to content

Commit 24c077f

Browse files
stofondrejmirtes
authored andcommitted
Fix support for classes named after pseudotypes in phpdoc
An object type will always be used if there is a use statement for it. The existence of the class is checked in the current namespace, if in a namespaced scope. If found, the object type is used. Otherwise, the pseudotype is used.
1 parent 0725f5a commit 24c077f

File tree

6 files changed

+192
-0
lines changed

6 files changed

+192
-0
lines changed

src/Analyser/NameScope.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ public function getUses(): array
4747
return $this->uses;
4848
}
4949

50+
public function hasUseAlias(string $name): bool
51+
{
52+
return isset($this->uses[strtolower($name)]);
53+
}
54+
5055
public function getClassName(): ?string
5156
{
5257
return $this->className;

src/PhpDoc/TypeNodeResolver.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,21 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco
152152
return new UnionType([new IntegerType(), new FloatType(), new StringType(), new BooleanType()]);
153153

154154
case 'number':
155+
$type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
156+
157+
if ($type !== null) {
158+
return $type;
159+
}
160+
155161
return new UnionType([new IntegerType(), new FloatType()]);
156162

157163
case 'numeric':
164+
$type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
165+
166+
if ($type !== null) {
167+
return $type;
168+
}
169+
158170
return new UnionType([
159171
new IntegerType(),
160172
new FloatType(),
@@ -171,7 +183,15 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco
171183
]);
172184

173185
case 'bool':
186+
return new BooleanType();
187+
174188
case 'boolean':
189+
$type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
190+
191+
if ($type !== null) {
192+
return $type;
193+
}
194+
175195
return new BooleanType();
176196

177197
case 'true':
@@ -184,7 +204,15 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco
184204
return new NullType();
185205

186206
case 'float':
207+
return new FloatType();
208+
187209
case 'double':
210+
$type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
211+
212+
if ($type !== null) {
213+
return $type;
214+
}
215+
188216
return new FloatType();
189217

190218
case 'array':
@@ -204,6 +232,12 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco
204232
return new CallableType();
205233

206234
case 'resource':
235+
$type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
236+
237+
if ($type !== null) {
238+
return $type;
239+
}
240+
207241
return new ResourceType();
208242

209243
case 'mixed':
@@ -216,6 +250,14 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco
216250
return new ObjectWithoutClassType();
217251

218252
case 'never':
253+
$type = $this->tryResolvePseudoTypeClassType($typeNode, $nameScope);
254+
255+
if ($type !== null) {
256+
return $type;
257+
}
258+
259+
return new NeverType(true);
260+
219261
case 'never-return':
220262
case 'never-returns':
221263
case 'no-return':
@@ -263,6 +305,25 @@ private function resolveIdentifierTypeNode(IdentifierTypeNode $typeNode, NameSco
263305
return new ObjectType($stringName);
264306
}
265307

308+
private function tryResolvePseudoTypeClassType(IdentifierTypeNode $typeNode, NameScope $nameScope): ?Type
309+
{
310+
if ($nameScope->hasUseAlias($typeNode->name)) {
311+
return new ObjectType($nameScope->resolveStringName($typeNode->name));
312+
}
313+
314+
if ($nameScope->getNamespace() === null) {
315+
return null;
316+
}
317+
318+
$className = $nameScope->resolveStringName($typeNode->name);
319+
320+
if ($this->getReflectionProvider()->hasClass($className)) {
321+
return new ObjectType($className);
322+
}
323+
324+
return null;
325+
}
326+
266327
private function resolveThisTypeNode(ThisTypeNode $typeNode, NameScope $nameScope): Type
267328
{
268329
$className = $nameScope->getClassName();

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5619,6 +5619,23 @@ public function dataArrayFunctions(): array
56195619
];
56205620
}
56215621

5622+
public function dataPseudoTypeOverrides(): array
5623+
{
5624+
return $this->gatherAssertTypes(__DIR__ . '/data/phpdoc-pseudotype-override.php');
5625+
}
5626+
5627+
public function dataPseudoTypeNamespace(): array
5628+
{
5629+
require_once __DIR__ . '/data/phpdoc-pseudotype-namespace.php';
5630+
5631+
return $this->gatherAssertTypes(__DIR__ . '/data/phpdoc-pseudotype-namespace.php');
5632+
}
5633+
5634+
public function dataPseudoTypeGlobal(): array
5635+
{
5636+
return $this->gatherAssertTypes(__DIR__ . '/data/phpdoc-pseudotype-global.php');
5637+
}
5638+
56225639
/**
56235640
* @dataProvider dataArrayFunctions
56245641
* @param string $description
@@ -11220,6 +11237,9 @@ private function gatherAssertTypes(string $file): array
1122011237
* @dataProvider dataNestedGenericIncompleteConstructor
1122111238
* @dataProvider dataIteratorIterator
1122211239
* @dataProvider dataBug4642
11240+
* @dataProvider dataPseudoTypeGlobal
11241+
* @dataProvider dataPseudoTypeNamespace
11242+
* @dataProvider dataPseudoTypeOverrides
1122311243
* @param string $assertType
1122411244
* @param string $file
1122511245
* @param mixed ...$args
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
use function PHPStan\Analyser\assertType;
4+
5+
function () {
6+
/** @var Number $number */
7+
$number = doFoo();
8+
9+
/** @var Boolean $boolean */
10+
$boolean = doFoo();
11+
12+
/** @var Numeric $numeric */
13+
$numeric = doFoo();
14+
15+
/** @var Never $never */
16+
$never = doFoo();
17+
18+
/** @var Resource $resource */
19+
$resource = doFoo();
20+
21+
/** @var Double $double */
22+
$double = doFoo();
23+
24+
assertType('float|int', $number);
25+
assertType('float|int|(string&numeric)', $numeric);
26+
assertType('bool', $boolean);
27+
assertType('resource', $resource);
28+
assertType('*NEVER*', $never);
29+
assertType('float', $double);
30+
};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace PhpdocPseudoTypesNamespace;
4+
5+
use function PHPStan\Analyser\assertType;
6+
7+
class Number {}
8+
class Numeric {}
9+
class Boolean {}
10+
class Resource {}
11+
class Never {}
12+
class Double {}
13+
14+
function () {
15+
/** @var Number $number */
16+
$number = doFoo();
17+
18+
/** @var Boolean $boolean */
19+
$boolean = doFoo();
20+
21+
/** @var Numeric $numeric */
22+
$numeric = doFoo();
23+
24+
/** @var Never $never */
25+
$never = doFoo();
26+
27+
/** @var Resource $resource */
28+
$resource = doFoo();
29+
30+
/** @var Double $double */
31+
$double = doFoo();
32+
33+
assertType('PhpdocPseudoTypesNamespace\Number', $number);
34+
assertType('PhpdocPseudoTypesNamespace\Numeric', $numeric);
35+
assertType('PhpdocPseudoTypesNamespace\Boolean', $boolean);
36+
assertType('PhpdocPseudoTypesNamespace\Resource', $resource);
37+
assertType('PhpdocPseudoTypesNamespace\Never', $never);
38+
assertType('PhpdocPseudoTypesNamespace\Double', $double);
39+
};
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
use Foo\Number;
4+
use Foo\Numeric;
5+
use Foo\Boolean;
6+
use Foo\Resource;
7+
use Foo\Never;
8+
use Foo\Double;
9+
10+
use function PHPStan\Analyser\assertType;
11+
12+
function () {
13+
/** @var Number $number */
14+
$number = doFoo();
15+
16+
/** @var Boolean $boolean */
17+
$boolean = doFoo();
18+
19+
/** @var Numeric $numeric */
20+
$numeric = doFoo();
21+
22+
/** @var Never $never */
23+
$never = doFoo();
24+
25+
/** @var Resource $resource */
26+
$resource = doFoo();
27+
28+
/** @var Double $double */
29+
$double = doFoo();
30+
31+
assertType('Foo\Number', $number);
32+
assertType('Foo\Numeric', $numeric);
33+
assertType('Foo\Boolean', $boolean);
34+
assertType('Foo\Resource', $resource);
35+
assertType('Foo\Never', $never);
36+
assertType('Foo\Double', $double);
37+
};

0 commit comments

Comments
 (0)