Skip to content

Commit 75ef74a

Browse files
mglamanclaude
andcommitted
Infer return type from class-string<T> argument in ClassResolver extensions
When a variable typed as class-string<T> is passed to ClassResolverInterface::getInstanceFromDefinition() or Drupal::classResolver(), PHPStan now infers the return type as T instead of falling back to object. The existing service-ID and constant-string class-name resolution via ServiceMap is unchanged. This only fills the gap for non-constant class-string<T> variables. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2552230 commit 75ef74a

File tree

2 files changed

+23
-0
lines changed

2 files changed

+23
-0
lines changed

src/Type/DrupalClassResolverReturnType.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use PHPStan\Reflection\MethodReflection;
1212
use PHPStan\Reflection\ParametersAcceptorSelector;
1313
use PHPStan\Type\ObjectType;
14+
use PHPStan\Type\ObjectWithoutClassType;
1415
use PHPStan\Type\Type;
1516
use function count;
1617

@@ -25,6 +26,12 @@ public static function getType(
2526
): Type {
2627
$arg1 = $scope->getType($methodCall->getArgs()[0]->value);
2728
if (count($arg1->getConstantStrings()) === 0) {
29+
// Handle class-string<T> typed variables
30+
$classStringObjectType = $arg1->getClassStringObjectType();
31+
if (!$classStringObjectType instanceof ObjectWithoutClassType) {
32+
return $classStringObjectType;
33+
}
34+
2835
return ParametersAcceptorSelector::selectFromArgs(
2936
$scope,
3037
$methodCall->getArgs(),

tests/src/Type/data/drupal-class-resolver.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@
88

99
class Foo {}
1010

11+
function test_class_string_vars(): void {
12+
/** @var class-string<Foo> $fooClass */
13+
$fooClass = Foo::class;
14+
assertType(Foo::class, (new ClassResolver())->getInstanceFromDefinition($fooClass));
15+
assertType(Foo::class, \Drupal::service('class_resolver')->getInstanceFromDefinition($fooClass));
16+
assertType(Foo::class, \Drupal::classResolver()->getInstanceFromDefinition($fooClass));
17+
assertType(Foo::class, \Drupal::classResolver($fooClass));
18+
19+
/** @var class-string<MyService> $serviceClass */
20+
$serviceClass = MyService::class;
21+
assertType(MyService::class, (new ClassResolver())->getInstanceFromDefinition($serviceClass));
22+
assertType(MyService::class, \Drupal::service('class_resolver')->getInstanceFromDefinition($serviceClass));
23+
assertType(MyService::class, \Drupal::classResolver()->getInstanceFromDefinition($serviceClass));
24+
assertType(MyService::class, \Drupal::classResolver($serviceClass));
25+
}
26+
1127
function test(): void {
1228
assertType(Foo::class, (new ClassResolver())->getInstanceFromDefinition(Foo::class));
1329
assertType(Foo::class, \Drupal::service('class_resolver')->getInstanceFromDefinition(Foo::class));

0 commit comments

Comments
 (0)