Skip to content

Commit 7145491

Browse files
committed
Fix calling non-static method without an object
1 parent b01e592 commit 7145491

File tree

2 files changed

+35
-16
lines changed

2 files changed

+35
-16
lines changed

src/Type/ReflectionHelperGetPrivateMethodInvokerReturnTypeExtension.php

+26-7
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828

2929
final class ReflectionHelperGetPrivateMethodInvokerReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
3030
{
31+
private const OBJECT_AS_STRING_CONTEXT = 0;
32+
private const OBJECT_AS_OBJECT_CONTEXT = 1;
33+
3134
/**
3235
* @param class-string $class
3336
*/
@@ -53,21 +56,25 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection,
5356
return null;
5457
}
5558

56-
$objectType = $scope->getType($args[0]->value)->getObjectTypeOrClassStringObjectType();
59+
$objectType = $scope->getType($args[0]->value);
5760
$methodType = $scope->getType($args[1]->value);
5861

59-
if (! $objectType->isObject()->yes()) {
60-
return new NeverType(true);
61-
}
62-
6362
return TypeTraverser::map($objectType, static function (Type $type, callable $traverse) use ($methodType, $scope, $args, $methodReflection): Type {
6463
if ($type instanceof UnionType || $type instanceof IntersectionType) {
6564
return $traverse($type);
6665
}
6766

67+
$context = self::OBJECT_AS_OBJECT_CONTEXT;
68+
69+
if ($type->isString()->yes()) {
70+
$context = self::OBJECT_AS_STRING_CONTEXT;
71+
}
72+
6873
$closures = [];
6974

70-
foreach ($type->getObjectClassReflections() as $classReflection) {
75+
$objectType = $type->getObjectTypeOrClassStringObjectType();
76+
77+
foreach ($objectType->getObjectClassReflections() as $classReflection) {
7178
foreach ($methodType->getConstantStrings() as $methodStringType) {
7279
$methodName = $methodStringType->getValue();
7380

@@ -86,7 +93,15 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection,
8693
$invokedMethodReflection->getNamedArgumentsVariants(),
8794
);
8895

89-
$returnType = strtolower($methodName) === '__construct' ? $type : $parametersAcceptor->getReturnType();
96+
if (! $invokedMethodReflection->isStatic() && $context === self::OBJECT_AS_STRING_CONTEXT) {
97+
// ReflectionException: Trying to invoke non static method FQCN::method() without an object
98+
$returnType = new NeverType(true);
99+
} elseif (strtolower($methodName) === '__construct') {
100+
// Do not use void as the return type of __construct
101+
$returnType = $objectType;
102+
} else {
103+
$returnType = $parametersAcceptor->getReturnType();
104+
}
90105

91106
$closures[] = new ClosureType(
92107
$parametersAcceptor->getParameters(),
@@ -99,6 +114,10 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection,
99114
}
100115

101116
if ($closures === []) {
117+
if (! $objectType->isObject()->yes()) {
118+
return new NeverType(true);
119+
}
120+
102121
return ParametersAcceptorSelector::selectFromArgs(
103122
$scope,
104123
$args,

tests/Type/data/reflection-helper.php

+9-9
Original file line numberDiff line numberDiff line change
@@ -64,26 +64,26 @@ public function testObjectAsObjectType(): void
6464

6565
public function testClassStringAsObjectType(): void
6666
{
67-
assertType('Closure(): void', self::getPrivateMethodInvoker(self::class, 'testOnFirstClassCallable'));
67+
assertType('Closure(): never', self::getPrivateMethodInvoker(self::class, 'testOnFirstClassCallable'));
6868

6969
$object = ModelReturnTypeTransformVisitor::class;
70-
assertType('Closure(PhpParser\Node): null', self::getPrivateMethodInvoker($object, 'enterNode'));
70+
assertType('Closure(PhpParser\Node): never', self::getPrivateMethodInvoker($object, 'enterNode'));
7171
assertType(
72-
'Closure(array<PhpParser\Node>): (array<PhpParser\Node>|null)',
72+
'Closure(array<PhpParser\Node>): never',
7373
self::getPrivateMethodInvoker($object, 'afterTraverse'),
7474
);
7575

7676
$object = FactoriesFunctionReturnTypeExtension::class;
7777
assertType(
78-
'Closure(CodeIgniter\PHPStan\Type\FactoriesReturnTypeHelper): CodeIgniter\PHPStan\Type\FactoriesFunctionReturnTypeExtension',
78+
'Closure(CodeIgniter\PHPStan\Type\FactoriesReturnTypeHelper): never',
7979
self::getPrivateMethodInvoker($object, '__construct'),
8080
);
8181
assertType(
82-
'Closure(PHPStan\Reflection\FunctionReflection): bool',
82+
'Closure(PHPStan\Reflection\FunctionReflection): never',
8383
self::getPrivateMethodInvoker($object, 'isFunctionSupported'),
8484
);
8585
assertType(
86-
'Closure(PHPStan\Reflection\FunctionReflection, PhpParser\Node\Expr\FuncCall, PHPStan\Analyser\Scope): (PHPStan\Type\Type|null)',
86+
'Closure(PHPStan\Reflection\FunctionReflection, PhpParser\Node\Expr\FuncCall, PHPStan\Analyser\Scope): never',
8787
self::getPrivateMethodInvoker($object, 'getTypeFromFunctionCall'),
8888
);
8989
}
@@ -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): CodeIgniter\Commands\Utilities\ConfigCheck',
135+
'Closure(Psr\Log\LoggerInterface, CodeIgniter\CLI\Commands): never',
136136
self::getPrivateMethodInvoker($class, '__construct'),
137137
);
138138
}
@@ -164,7 +164,7 @@ public function testOnUnionOfObjects(object|string $object): void
164164
assertType(
165165
sprintf(
166166
'%s|%s',
167-
'(Closure(CodeIgniter\PHPStan\Type\ServicesReturnTypeHelper): CodeIgniter\PHPStan\Type\ServicesFunctionReturnTypeExtension)',
167+
'(Closure(CodeIgniter\PHPStan\Type\ServicesReturnTypeHelper): never)',
168168
'(Closure(non-empty-string): CodeIgniter\PHPStan\Tests\Fixtures\Type\ReflectionHelperGetPrivateMethodInvokerTest)',
169169
),
170170
self::getPrivateMethodInvoker($object, '__construct'),
@@ -177,7 +177,7 @@ public function testOnUnionOfObjects(object|string $object): void
177177
public function testOnUnionOfStringObjectsWithOneNonClass(string $object): void
178178
{
179179
assertType(
180-
'*NEVER*',
180+
'Closure(Psr\Log\LoggerInterface, CodeIgniter\CLI\Commands): never',
181181
self::getPrivateMethodInvoker($object, '__construct'),
182182
);
183183
}

0 commit comments

Comments
 (0)