Skip to content

Commit 0d5be5a

Browse files
authored
ContainerDynamicReturnTypeExtension resolves constant string types (#574)
1 parent d5010b7 commit 0d5be5a

File tree

3 files changed

+35
-47
lines changed

3 files changed

+35
-47
lines changed

src/Type/ContainerDynamicReturnTypeExtension.php

Lines changed: 16 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,23 @@
22

33
namespace mglaman\PHPStanDrupal\Type;
44

5-
use mglaman\PHPStanDrupal\Drupal\DrupalServiceDefinition;
65
use mglaman\PHPStanDrupal\Drupal\ServiceMap;
7-
use PhpParser\Node;
8-
use PhpParser\Node\Expr\ClassConstFetch;
96
use PhpParser\Node\Expr\MethodCall;
10-
use PhpParser\Node\Name\FullyQualified;
11-
use PhpParser\Node\Scalar\String_;
12-
use PhpParser\Node\VariadicPlaceholder;
137
use PHPStan\Analyser\Scope;
148
use PHPStan\Reflection\MethodReflection;
159
use PHPStan\Reflection\ParametersAcceptorSelector;
16-
use PHPStan\ShouldNotHappenException;
1710
use PHPStan\Type\Constant\ConstantBooleanType;
1811
use PHPStan\Type\DynamicMethodReturnTypeExtension;
12+
use PHPStan\Type\Type;
13+
use PHPStan\Type\TypeCombinator;
1914
use Psr\Container\ContainerInterface;
2015

2116
class ContainerDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
2217
{
2318
/**
2419
* @var ServiceMap
2520
*/
26-
private $serviceMap;
21+
private ServiceMap $serviceMap;
2722

2823
public function __construct(ServiceMap $serviceMap)
2924
{
@@ -44,49 +39,26 @@ public function getTypeFromMethodCall(
4439
MethodReflection $methodReflection,
4540
MethodCall $methodCall,
4641
Scope $scope
47-
): \PHPStan\Type\Type {
42+
): Type {
4843
$returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
49-
if (!isset($methodCall->args[0])) {
44+
$args = $methodCall->getArgs();
45+
if (count($args) !== 1) {
5046
return $returnType;
5147
}
5248

53-
$arg1 = $methodCall->args[0];
54-
if ($arg1 instanceof VariadicPlaceholder) {
55-
throw new ShouldNotHappenException();
56-
}
57-
$arg1 = $arg1->value;
58-
59-
$serviceId = $this->getServiceId($arg1);
60-
if ($serviceId === null) {
61-
return $returnType;
62-
}
49+
$methodName = $methodReflection->getName();
50+
$types = [];
51+
$argType = $scope->getType($args[0]->value);
6352

64-
if ($methodReflection->getName() === 'get') {
53+
foreach ($argType->getConstantStrings() as $constantStringType) {
54+
$serviceId = $constantStringType->getValue();
6555
$service = $this->serviceMap->getService($serviceId);
66-
if ($service instanceof DrupalServiceDefinition) {
67-
return $service->getType();
56+
if ($methodName === 'get') {
57+
$types[] = $service !== null ? $service->getType() : $returnType;
58+
} elseif ($methodName === 'has') {
59+
$types[] = new ConstantBooleanType($service !== null);
6860
}
69-
return $returnType;
70-
}
71-
72-
if ($methodReflection->getName() === 'has') {
73-
return new ConstantBooleanType($this->serviceMap->getService($serviceId) instanceof DrupalServiceDefinition);
7461
}
75-
76-
throw new ShouldNotHappenException();
77-
}
78-
79-
protected function getServiceId(Node $arg1): ?string
80-
{
81-
if ($arg1 instanceof String_) {
82-
// @todo determine what these types are.
83-
return $arg1->value;
84-
}
85-
86-
if ($arg1 instanceof ClassConstFetch && $arg1->class instanceof FullyQualified) {
87-
return (string) $arg1->class;
88-
}
89-
90-
return null;
62+
return TypeCombinator::union(...$types);
9163
}
9264
}

tests/src/Type/DrupalContainerDynamicReturnTypeTest.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ final class DrupalContainerDynamicReturnTypeTest extends TypeInferenceTestCase
1313

1414
public function dataFileAsserts(): iterable
1515
{
16-
yield from $this->gatherAssertTypes(__DIR__ . '/data/container.php');
17-
yield from $this->gatherAssertTypes(__DIR__ . '/data/drupal-service-static.php');
18-
yield from $this->gatherAssertTypes(__DIR__ . '/data/drupal-class-resolver.php');
16+
yield from self::gatherAssertTypes(__DIR__ . '/data/container.php');
17+
yield from self::gatherAssertTypes(__DIR__ . '/data/drupal-service-static.php');
18+
yield from self::gatherAssertTypes(__DIR__ . '/data/drupal-class-resolver.php');
19+
yield from self::gatherAssertTypes(__DIR__ . '/data/bug-563.php');
1920
}
2021

2122
/**

tests/src/Type/data/bug-563.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Bug563;
4+
5+
use Drupal\Core\Cache\CacheBackendInterface;
6+
use function PHPStan\Testing\assertType;
7+
8+
function foo(): void {
9+
$container = \Drupal::getContainer();
10+
assert($container !== null);
11+
$cache_bins = ['page', 'dynamic_page_cache', 'render'];
12+
foreach ($cache_bins as $cache_bin) {
13+
assertType(CacheBackendInterface::class, $container->get("cache.$cache_bin"));
14+
}
15+
}

0 commit comments

Comments
 (0)