diff --git a/src/Type/ContainerDynamicReturnTypeExtension.php b/src/Type/ContainerDynamicReturnTypeExtension.php index 3a071ed7..ad3aa26d 100644 --- a/src/Type/ContainerDynamicReturnTypeExtension.php +++ b/src/Type/ContainerDynamicReturnTypeExtension.php @@ -67,6 +67,12 @@ public function getTypeFromMethodCall( } } + // A dynamic service ID has no constant strings; unioning zero + // types would produce `never`. + if ($types === []) { + return $returnType; + } + return TypeCombinator::union(...$types); } elseif ($methodName === 'get') { $args = $methodCall->getArgs(); @@ -89,12 +95,20 @@ public function getTypeFromMethodCall( $argType = $scope->getType($args[0]->value); - foreach ($argType->getConstantStrings() as $constantStringType) { + $constantStrings = $argType->getConstantStrings(); + foreach ($constantStrings as $constantStringType) { $serviceId = $constantStringType->getValue(); $service = $this->serviceMap->getService($serviceId); $types[] = $service !== null ? $service->getType() : $returnType; } + // A dynamic service ID has no constant strings; fall back to the + // declared return type so the union cannot collapse to `never`, + // while keeping any null added for NULL_ON_INVALID_REFERENCE. + if ($constantStrings === []) { + $types[] = $returnType; + } + return TypeCombinator::union(...$types); } diff --git a/tests/src/Rules/EntityStoragePropertyAssignmentRuleTest.php b/tests/src/Rules/EntityStoragePropertyAssignmentRuleTest.php index 451afb5f..aba4ea6f 100644 --- a/tests/src/Rules/EntityStoragePropertyAssignmentRuleTest.php +++ b/tests/src/Rules/EntityStoragePropertyAssignmentRuleTest.php @@ -44,4 +44,9 @@ public function testRule(): void ] ); } + + public function testBug1012(): void + { + $this->analyse([__DIR__ . '/data/bug-1012-proxy.php'], []); + } } diff --git a/tests/src/Rules/data/bug-1012-proxy.php b/tests/src/Rules/data/bug-1012-proxy.php new file mode 100644 index 00000000..e30634ef --- /dev/null +++ b/tests/src/Rules/data/bug-1012-proxy.php @@ -0,0 +1,33 @@ +container = $container; + $this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id; + } + + protected function lazyLoadItself(): object + { + if (!isset($this->service)) { + $this->service = $this->container->get($this->drupalProxyOriginalServiceId); + } + + return $this->service; + } +} diff --git a/tests/src/Type/DrupalContainerDynamicReturnTypeTest.php b/tests/src/Type/DrupalContainerDynamicReturnTypeTest.php index 10e29aa6..73db87ce 100644 --- a/tests/src/Type/DrupalContainerDynamicReturnTypeTest.php +++ b/tests/src/Type/DrupalContainerDynamicReturnTypeTest.php @@ -17,6 +17,7 @@ public static function dataFileAsserts(): iterable yield from self::gatherAssertTypes(__DIR__ . '/data/drupal-service-static.php'); yield from self::gatherAssertTypes(__DIR__ . '/data/drupal-class-resolver.php'); yield from self::gatherAssertTypes(__DIR__ . '/data/bug-563.php'); + yield from self::gatherAssertTypes(__DIR__ . '/data/bug-1012.php'); yield from self::gatherAssertTypes(__DIR__ . '/data/container-optional.php'); yield from self::gatherAssertTypes(__DIR__ . '/data/synthetic.php'); } diff --git a/tests/src/Type/data/bug-1012.php b/tests/src/Type/data/bug-1012.php new file mode 100644 index 00000000..474252b5 --- /dev/null +++ b/tests/src/Type/data/bug-1012.php @@ -0,0 +1,15 @@ +get($service_id)); + assertType('object|null', $container->get($service_id, ContainerInterface::NULL_ON_INVALID_REFERENCE)); + assertType('bool', $container->has($service_id)); +}