Skip to content

Commit a904c8e

Browse files
authored
ReflectionExtension: union type support (#39)
1 parent fdac83b commit a904c8e

File tree

2 files changed

+76
-16
lines changed

2 files changed

+76
-16
lines changed

src/Extension/ReflectionExtension.php

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
use PHPStan\Broker\Broker;
1111
use PHPStan\Broker\ClassNotFoundException;
1212
use PHPStan\Reflection\MethodReflection;
13-
use PHPStan\Type\Constant\ConstantStringType;
13+
use PHPStan\Type\NeverType;
1414
use PHPStan\Type\ObjectType;
1515
use PHPStan\Type\Type;
16+
use PHPStan\Type\TypeCombinator;
17+
use PHPStan\Type\TypeUtils;
1618
use PHPStan\Type\VoidType;
1719
use ReflectionClass;
1820
use ReflectionException;
@@ -65,12 +67,21 @@ public function getThrowTypeFromConstructor(MethodReflection $methodReflection,
6567
private function resolveReflectionClass(New_ $newNode, Scope $scope): Type
6668
{
6769
$reflectionExceptionType = new ObjectType(ReflectionException::class);
68-
$valueType = $scope->getType($newNode->args[0]->value);
69-
if (!$valueType instanceof ConstantStringType) {
70+
if (!isset($newNode->args[0])) {
7071
return $reflectionExceptionType;
7172
}
7273

73-
if (!$this->broker->hasClass($valueType->getValue())) {
74+
$valueType = $scope->getType($newNode->args[0]->value);
75+
76+
foreach (TypeUtils::getConstantStrings($valueType) as $constantString) {
77+
if (!$this->broker->hasClass($constantString->getValue())) {
78+
return $reflectionExceptionType;
79+
}
80+
81+
$valueType = TypeCombinator::remove($valueType, $constantString);
82+
}
83+
84+
if (!$valueType instanceof NeverType) {
7485
return $reflectionExceptionType;
7586
}
7687

@@ -80,12 +91,20 @@ private function resolveReflectionClass(New_ $newNode, Scope $scope): Type
8091
private function resolveReflectionFunction(New_ $newNode, Scope $scope): Type
8192
{
8293
$reflectionExceptionType = new ObjectType(ReflectionException::class);
83-
$valueType = $scope->getType($newNode->args[0]->value);
84-
if (!$valueType instanceof ConstantStringType) {
94+
if (!isset($newNode->args[0])) {
8595
return $reflectionExceptionType;
8696
}
8797

88-
if (!$this->broker->hasFunction(new Name($valueType->getValue()), $scope)) {
98+
$valueType = $scope->getType($newNode->args[0]->value);
99+
foreach (TypeUtils::getConstantStrings($valueType) as $constantString) {
100+
if (!$this->broker->hasFunction(new Name($constantString->getValue()), $scope)) {
101+
return $reflectionExceptionType;
102+
}
103+
104+
$valueType = TypeCombinator::remove($valueType, $constantString);
105+
}
106+
107+
if (!$valueType instanceof NeverType) {
89108
return $reflectionExceptionType;
90109
}
91110

@@ -95,21 +114,39 @@ private function resolveReflectionFunction(New_ $newNode, Scope $scope): Type
95114
private function resolveReflectionProperty(New_ $newNode, Scope $scope): Type
96115
{
97116
$reflectionExceptionType = new ObjectType(ReflectionException::class);
98-
$valueType = $scope->getType($newNode->args[0]->value);
99-
if (!$valueType instanceof ConstantStringType) {
117+
if (!isset($newNode->args[1])) {
100118
return $reflectionExceptionType;
101119
}
102120

121+
$valueType = $scope->getType($newNode->args[0]->value);
103122
$propertyType = $scope->getType($newNode->args[1]->value);
104-
if (!$propertyType instanceof ConstantStringType) {
123+
foreach (TypeUtils::getConstantStrings($valueType) as $constantString) {
124+
if (!$this->broker->hasClass($constantString->getValue())) {
125+
return $reflectionExceptionType;
126+
}
127+
128+
foreach (TypeUtils::getConstantStrings($propertyType) as $constantPropertyString) {
129+
try {
130+
if (!$this->broker->getClass($constantString->getValue())->hasProperty($constantPropertyString->getValue())) {
131+
return $reflectionExceptionType;
132+
}
133+
} catch (ClassNotFoundException $e) {
134+
return $reflectionExceptionType;
135+
}
136+
}
137+
138+
$valueType = TypeCombinator::remove($valueType, $constantString);
139+
}
140+
141+
foreach (TypeUtils::getConstantStrings($propertyType) as $constantPropertyString) {
142+
$propertyType = TypeCombinator::remove($propertyType, $constantPropertyString);
143+
}
144+
145+
if (!$valueType instanceof NeverType) {
105146
return $reflectionExceptionType;
106147
}
107148

108-
try {
109-
if (!$this->broker->getClass($valueType->getValue())->hasProperty($propertyType->getValue())) {
110-
return $reflectionExceptionType;
111-
}
112-
} catch (ClassNotFoundException $e) {
149+
if (!$propertyType instanceof NeverType) {
113150
return $reflectionExceptionType;
114151
}
115152

tests/src/Rules/data/throws-php-internal-functions.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,29 @@
44

55
use DateTime;
66
use DateTimeImmutable;
7-
use function rand;
87
use ReflectionClass;
98
use ReflectionFunction;
109
use ReflectionProperty;
1110
use ReflectionZendExtension;
11+
use Throwable;
12+
use function rand;
13+
14+
class ValueObject
15+
{
16+
17+
private $property;
18+
19+
private $secondProperty;
20+
21+
}
1222

1323
class Example
1424
{
1525

1626
private $property;
1727

28+
private $secondProperty;
29+
1830
public function testReflection(): void
1931
{
2032
new ReflectionClass(self::class);
@@ -29,6 +41,17 @@ public function testReflection(): void
2941
new ReflectionFunction('undefinedFunction'); // error: Missing @throws ReflectionException annotation
3042

3143
new ReflectionZendExtension('unknownZendExtension'); // error: Missing @throws ReflectionException annotation
44+
45+
new ReflectionClass(rand(0, 1) === 0 ? self::class : Throwable::class);
46+
new ReflectionClass(rand(0, 1) === 0 ? self::class : null); // error: Missing @throws ReflectionException annotation
47+
new ReflectionClass(rand(0, 1) === 0 ? self::class : 'undefinedClass'); // error: Missing @throws ReflectionException annotation
48+
49+
new ReflectionProperty(rand(0, 1) === 0 ? self::class : ValueObject::class, rand(0, 1) === 0 ? 'property' : 'secondProperty');
50+
new ReflectionProperty(rand(0, 1) === 0 ? self::class : null, rand(0, 1) === 0 ? 'property' : 'secondProperty'); // error: Missing @throws ReflectionException annotation
51+
new ReflectionProperty(rand(0, 1) === 0 ? self::class : Throwable::class, rand(0, 1) === 0 ? 'property' : 'undefinedProperty'); // error: Missing @throws ReflectionException annotation
52+
53+
new ReflectionFunction(rand(0, 1) === 0 ? 'count' : 'sort');
54+
new ReflectionFunction(rand(0, 1) === 0 ? 'count' : 'undefinedFunction'); // error: Missing @throws ReflectionException annotation
3255
}
3356

3457
public function testDateTime(): void

0 commit comments

Comments
 (0)