diff --git a/src/Symfony/DefaultParameterMap.php b/src/Symfony/DefaultParameterMap.php index fe29b34f..26317468 100644 --- a/src/Symfony/DefaultParameterMap.php +++ b/src/Symfony/DefaultParameterMap.php @@ -4,8 +4,9 @@ use PhpParser\Node\Expr; use PHPStan\Analyser\Scope; +use PHPStan\Type\Type; use PHPStan\Type\TypeUtils; -use function count; +use function array_map; final class DefaultParameterMap implements ParameterMap { @@ -34,10 +35,13 @@ public function getParameter(string $key): ?ParameterDefinition return $this->parameters[$key] ?? null; } - public static function getParameterKeyFromNode(Expr $node, Scope $scope): ?string + public static function getParameterKeysFromNode(Expr $node, Scope $scope): array { $strings = TypeUtils::getConstantStrings($scope->getType($node)); - return count($strings) === 1 ? $strings[0]->getValue() : null; + + return array_map(static function (Type $type) { + return $type->getValue(); + }, $strings); } } diff --git a/src/Symfony/FakeParameterMap.php b/src/Symfony/FakeParameterMap.php index 0bf450e7..53acdc3c 100644 --- a/src/Symfony/FakeParameterMap.php +++ b/src/Symfony/FakeParameterMap.php @@ -21,9 +21,9 @@ public function getParameter(string $key): ?ParameterDefinition return null; } - public static function getParameterKeyFromNode(Expr $node, Scope $scope): ?string + public static function getParameterKeysFromNode(Expr $node, Scope $scope): array { - return null; + return []; } } diff --git a/src/Symfony/ParameterMap.php b/src/Symfony/ParameterMap.php index c768e2f2..ff0f5224 100644 --- a/src/Symfony/ParameterMap.php +++ b/src/Symfony/ParameterMap.php @@ -15,6 +15,9 @@ public function getParameters(): array; public function getParameter(string $key): ?ParameterDefinition; - public static function getParameterKeyFromNode(Expr $node, Scope $scope): ?string; + /** + * @return array + */ + public static function getParameterKeysFromNode(Expr $node, Scope $scope): array; } diff --git a/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php index 6a197a8f..b18a386f 100644 --- a/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php +++ b/src/Type/Symfony/ParameterDynamicReturnTypeExtension.php @@ -113,7 +113,7 @@ private function getGetTypeFromMethodCall( { // We don't use the method's return type because this won't work properly with lowest and // highest versions of Symfony ("mixed" for lowest, "array|bool|float|integer|string|null" for highest). - $returnType = new UnionType([ + $defaultReturnType = new UnionType([ new ArrayType(new MixedType(), new MixedType()), new BooleanType(), new FloatType(), @@ -122,18 +122,25 @@ private function getGetTypeFromMethodCall( new NullType(), ]); if (!isset($methodCall->getArgs()[0])) { - return $returnType; + return $defaultReturnType; } - $parameterKey = $this->parameterMap::getParameterKeyFromNode($methodCall->getArgs()[0]->value, $scope); - if ($parameterKey !== null) { + $parameterKeys = $this->parameterMap::getParameterKeysFromNode($methodCall->getArgs()[0]->value, $scope); + if ($parameterKeys === []) { + return $defaultReturnType; + } + + $returnTypes = []; + foreach ($parameterKeys as $parameterKey) { $parameter = $this->parameterMap->getParameter($parameterKey); - if ($parameter !== null) { - return $this->generalizeTypeFromValue($scope, $parameter->getValue()); + if ($parameter === null) { + return $defaultReturnType; } + + $returnTypes[] = $this->generalizeTypeFromValue($scope, $parameter->getValue()); } - return $returnType; + return TypeCombinator::union(...$returnTypes); } /** @@ -211,18 +218,31 @@ private function getHasTypeFromMethodCall( Scope $scope ): Type { - $returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + $defaultReturnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); if (!isset($methodCall->getArgs()[0]) || !$this->constantHassers) { - return $returnType; + return $defaultReturnType; } - $parameterKey = $this->parameterMap::getParameterKeyFromNode($methodCall->getArgs()[0]->value, $scope); - if ($parameterKey !== null) { + $parameterKeys = $this->parameterMap::getParameterKeysFromNode($methodCall->getArgs()[0]->value, $scope); + if ($parameterKeys === []) { + return $defaultReturnType; + } + + $has = null; + foreach ($parameterKeys as $parameterKey) { $parameter = $this->parameterMap->getParameter($parameterKey); - return new ConstantBooleanType($parameter !== null); + + if ($has === null) { + $has = $parameter !== null; + } elseif ( + ($has === true && $parameter === null) + || ($has === false && $parameter !== null) + ) { + return $defaultReturnType; + } } - return $returnType; + return new ConstantBooleanType($has); } } diff --git a/tests/Type/Symfony/data/ExampleController.php b/tests/Type/Symfony/data/ExampleController.php index 89d2b5f8..528b06a7 100644 --- a/tests/Type/Symfony/data/ExampleController.php +++ b/tests/Type/Symfony/data/ExampleController.php @@ -116,6 +116,27 @@ public function parameters(ContainerInterface $container, ParameterBagInterface assertType('true', $parameterBag->has('app.binary')); assertType('true', $container->hasParameter('app.constant')); assertType('true', $parameterBag->has('app.constant')); + + $key = rand(0, 1) ? 'app.string' : 'app.int'; + assertType("int|string", $container->getParameter($key)); + assertType("int|string", $parameterBag->get($key)); + assertType("int|string", $this->getParameter($key)); + assertType('true', $container->hasParameter($key)); + assertType('true', $parameterBag->has($key)); + + $key = rand(0, 1) ? 'app.string' : 'app.foo'; + assertType("array|bool|float|int|string|null", $container->getParameter($key)); + assertType("array|bool|float|int|string|null", $parameterBag->get($key)); + assertType("array|bool|float|int|string|null", $this->getParameter($key)); + assertType('bool', $container->hasParameter($key)); + assertType('bool', $parameterBag->has($key)); + + $key = rand(0, 1) ? 'app.bar' : 'app.foo'; + assertType("array|bool|float|int|string|null", $container->getParameter($key)); + assertType("array|bool|float|int|string|null", $parameterBag->get($key)); + assertType("array|bool|float|int|string|null", $this->getParameter($key)); + assertType('false', $container->hasParameter($key)); + assertType('false', $parameterBag->has($key)); } }