diff --git a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php index 1c59bbbf23..cd324b00b4 100644 --- a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php +++ b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php @@ -7,11 +7,15 @@ use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\Accessory\AccessoryLiteralStringType; use PHPStan\Type\Accessory\AccessoryNonEmptyStringType; +use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantStringType; +use PHPStan\Type\ConstantScalarType; use PHPStan\Type\DynamicFunctionReturnTypeExtension; use PHPStan\Type\IntersectionType; use PHPStan\Type\StringType; use PHPStan\Type\Type; use function count; +use function implode; use function in_array; class ImplodeFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension @@ -35,20 +39,7 @@ public function getTypeFromFunctionCall( if (count($args) === 1) { $argType = $scope->getType($args[0]->value); if ($argType->isArray()->yes()) { - $accessoryTypes = []; - if ($argType->isIterableAtLeastOnce()->yes() && $argType->getIterableValueType()->isNonEmptyString()->yes()) { - $accessoryTypes[] = new AccessoryNonEmptyStringType(); - } - if ($argType->getIterableValueType()->isLiteralString()->yes()) { - $accessoryTypes[] = new AccessoryLiteralStringType(); - } - - if (count($accessoryTypes) > 0) { - $accessoryTypes[] = new StringType(); - return new IntersectionType($accessoryTypes); - } - - return new StringType(); + return $this->implode($argType, new ConstantStringType('')); } } @@ -58,6 +49,19 @@ public function getTypeFromFunctionCall( $separatorType = $scope->getType($args[0]->value); $arrayType = $scope->getType($args[1]->value); + + return $this->implode($arrayType, $separatorType); + } + + private function implode(Type $arrayType, Type $separatorType): Type + { + if ($arrayType instanceof ConstantArrayType && $separatorType instanceof ConstantStringType) { + $constantType = $this->inferConstantType($arrayType, $separatorType); + if ($constantType !== null) { + return $constantType; + } + } + $accessoryTypes = []; if ($arrayType->isIterableAtLeastOnce()->yes()) { if ($arrayType->getIterableValueType()->isNonEmptyString()->yes() || $separatorType->isNonEmptyString()->yes()) { @@ -77,4 +81,19 @@ public function getTypeFromFunctionCall( return new StringType(); } + private function inferConstantType(ConstantArrayType $arrayType, ConstantStringType $separatorType): ?Type + { + $valueTypes = $arrayType->getValueTypes(); + + $arrayValues = []; + foreach ($valueTypes as $valueType) { + if (!$valueType instanceof ConstantScalarType) { + return null; + } + $arrayValues[] = $valueType->getValue(); + } + + return new ConstantStringType(implode($separatorType->getValue(), $arrayValues)); + } + } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index aaf6bc04b3..9f1ed697ec 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -14,6 +14,9 @@ class NodeScopeResolverTest extends TypeInferenceTestCase public function dataFileAsserts(): iterable { + require_once __DIR__ . '/data/implode.php'; + yield from $this->gatherAssertTypes(__DIR__ . '/data/implode.php'); + require_once __DIR__ . '/data/bug2574.php'; yield from $this->gatherAssertTypes(__DIR__ . '/data/bug2574.php'); diff --git a/tests/PHPStan/Analyser/data/bug-5219.php b/tests/PHPStan/Analyser/data/bug-5219.php index cf7e4c0485..91ce62bb02 100644 --- a/tests/PHPStan/Analyser/data/bug-5219.php +++ b/tests/PHPStan/Analyser/data/bug-5219.php @@ -7,9 +7,9 @@ class HelloWorld { - protected function foo(string $message): void + protected function foo(string $message, string $x): void { - $header = sprintf('%s-%s', '', implode('-', ['x'])); + $header = sprintf('%s-%s', '', implode('-', [$x])); assertType('non-empty-string', $header); assertType('non-empty-array', [$header => $message]); diff --git a/tests/PHPStan/Analyser/data/implode.php b/tests/PHPStan/Analyser/data/implode.php new file mode 100644 index 0000000000..b2e2b9aa7c --- /dev/null +++ b/tests/PHPStan/Analyser/data/implode.php @@ -0,0 +1,24 @@ +