Skip to content

Commit 15e47ee

Browse files
committed
Reflected __construct should have object as return type
1 parent 78bef1a commit 15e47ee

File tree

2 files changed

+53
-33
lines changed

2 files changed

+53
-33
lines changed

src/Type/ReflectionHelperGetPrivateMethodInvokerReturnTypeExtension.php

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
use PHPStan\Reflection\ParametersAcceptorSelector;
2020
use PHPStan\Type\ClosureType;
2121
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
22+
use PHPStan\Type\IntersectionType;
2223
use PHPStan\Type\NeverType;
2324
use PHPStan\Type\Type;
2425
use PHPStan\Type\TypeCombinator;
26+
use PHPStan\Type\TypeTraverser;
27+
use PHPStan\Type\UnionType;
2528

2629
final class ReflectionHelperGetPrivateMethodInvokerReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
2730
{
@@ -53,44 +56,57 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection,
5356
$objectType = $scope->getType($args[0]->value)->getObjectTypeOrClassStringObjectType();
5457
$methodType = $scope->getType($args[1]->value);
5558

56-
if ($objectType->getObjectClassReflections() === [] && ! $objectType->isObject()->yes()) {
59+
if (! $objectType->isObject()->yes()) {
5760
return new NeverType(true);
5861
}
5962

60-
$closures = [];
63+
return TypeTraverser::map($objectType, static function (Type $type, callable $traverse) use ($methodType, $scope, $args, $methodReflection): Type {
64+
if ($type instanceof UnionType || $type instanceof IntersectionType) {
65+
return $traverse($type);
66+
}
67+
68+
$closures = [];
69+
70+
foreach ($type->getObjectClassReflections() as $classReflection) {
71+
foreach ($methodType->getConstantStrings() as $methodStringType) {
72+
$methodName = $methodStringType->getValue();
73+
74+
if (! $classReflection->hasMethod($methodName)) {
75+
$closures[] = new NeverType(true);
76+
77+
continue;
78+
}
79+
80+
$invokedMethodReflection = $classReflection->getMethod($methodName, $scope);
6181

62-
foreach ($objectType->getObjectClassReflections() as $classReflection) {
63-
foreach ($methodType->getConstantStrings() as $methodStringType) {
64-
$methodName = $methodStringType->getValue();
82+
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
83+
$scope,
84+
[],
85+
$invokedMethodReflection->getVariants(),
86+
$invokedMethodReflection->getNamedArgumentsVariants(),
87+
);
6588

66-
if (! $classReflection->hasMethod($methodName)) {
67-
$closures[] = new NeverType(true);
89+
$returnType = strtolower($methodName) === '__construct' ? $type : $parametersAcceptor->getReturnType();
6890

69-
continue;
91+
$closures[] = new ClosureType(
92+
$parametersAcceptor->getParameters(),
93+
$returnType,
94+
$parametersAcceptor->isVariadic(),
95+
$parametersAcceptor->getTemplateTypeMap(),
96+
$parametersAcceptor->getResolvedTemplateTypeMap(),
97+
);
7098
}
99+
}
71100

72-
$methodReflection = $classReflection->getMethod($methodName, $scope);
73-
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
101+
if ($closures === []) {
102+
return ParametersAcceptorSelector::selectFromArgs(
74103
$scope,
75104
$args,
76105
$methodReflection->getVariants(),
77-
$methodReflection->getNamedArgumentsVariants(),
78-
);
79-
80-
$closures[] = new ClosureType(
81-
$parametersAcceptor->getParameters(),
82-
$parametersAcceptor->getReturnType(),
83-
$parametersAcceptor->isVariadic(),
84-
$parametersAcceptor->getTemplateTypeMap(),
85-
$parametersAcceptor->getResolvedTemplateTypeMap(),
86-
);
106+
)->getReturnType();
87107
}
88-
}
89-
90-
if ($closures === []) {
91-
return null;
92-
}
93108

94-
return TypeCombinator::union(...$closures);
109+
return TypeCombinator::union(...$closures);
110+
});
95111
}
96112
}

tests/Type/data/reflection-helper.php

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public function testObjectAsObjectType(): void
5959
self::getPrivateMethodInvoker($object, 'run'),
6060
);
6161
assertType('Closure(object): string', self::getPrivateMethodInvoker($object, 'getVarDump'));
62-
assertType('Closure(object): string', self::getPrivateMethodInvoker($object, 'getKIntD'));
62+
assertType('Closure(object): string', self::getPrivateMethodInvoker($object, 'getKintD'));
6363
}
6464

6565
public function testClassStringAsObjectType(): void
@@ -75,7 +75,7 @@ public function testClassStringAsObjectType(): void
7575

7676
$object = FactoriesFunctionReturnTypeExtension::class;
7777
assertType(
78-
'Closure(CodeIgniter\PHPStan\Type\FactoriesReturnTypeHelper): void',
78+
'Closure(CodeIgniter\PHPStan\Type\FactoriesReturnTypeHelper): CodeIgniter\PHPStan\Type\FactoriesFunctionReturnTypeExtension',
7979
self::getPrivateMethodInvoker($object, '__construct'),
8080
);
8181
assertType(
@@ -132,7 +132,7 @@ public function testOnClassString(string $object): void
132132
public function testOnGenericClassString(string $class): void
133133
{
134134
assertType(
135-
'Closure(Psr\Log\LoggerInterface, CodeIgniter\CLI\Commands): void',
135+
'Closure(Psr\Log\LoggerInterface, CodeIgniter\CLI\Commands): CodeIgniter\Commands\Utilities\ConfigCheck',
136136
self::getPrivateMethodInvoker($class, '__construct'),
137137
);
138138
}
@@ -146,12 +146,12 @@ public function testOnObject(object $object): void
146146
}
147147

148148
/**
149-
* @param self $object
149+
* @param $this $object
150150
*/
151151
public function testOnObjectWithClassType(object $object): void
152152
{
153153
assertType(
154-
'Closure(non-empty-string): void',
154+
'Closure(non-empty-string): $this',
155155
self::getPrivateMethodInvoker($object, '__construct'),
156156
);
157157
}
@@ -162,7 +162,11 @@ public function testOnObjectWithClassType(object $object): void
162162
public function testOnUnionOfObjects(object|string $object): void
163163
{
164164
assertType(
165-
'(Closure(CodeIgniter\PHPStan\Type\ServicesReturnTypeHelper): void)|(Closure(non-empty-string): void)',
165+
sprintf(
166+
'%s|%s',
167+
'(Closure(CodeIgniter\PHPStan\Type\ServicesReturnTypeHelper): CodeIgniter\PHPStan\Type\ServicesFunctionReturnTypeExtension)',
168+
'(Closure(non-empty-string): CodeIgniter\PHPStan\Tests\Fixtures\Type\ReflectionHelperGetPrivateMethodInvokerTest)',
169+
),
166170
self::getPrivateMethodInvoker($object, '__construct'),
167171
);
168172
}
@@ -184,7 +188,7 @@ public function testOnUnionOfStringObjectsWithOneNonClass(string $object): void
184188
public function testOnUnionOfMethods(string $method): void
185189
{
186190
assertType(
187-
'(Closure(): void)|(Closure(non-empty-string): void)',
191+
'(Closure(): void)|(Closure(non-empty-string): $this)',
188192
self::getPrivateMethodInvoker($this, $method),
189193
);
190194
}

0 commit comments

Comments
 (0)