diff --git a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php index 4f38ed5137..a23d8443e1 100644 --- a/src/Type/Php/ImplodeFunctionReturnTypeExtension.php +++ b/src/Type/Php/ImplodeFunctionReturnTypeExtension.php @@ -62,10 +62,11 @@ public function getTypeFromFunctionCall( private function implode(Type $arrayType, Type $separatorType): Type { if (count($arrayType->getConstantArrays()) > 0 && count($separatorType->getConstantStrings()) > 0) { + $isNonEmpty = $arrayType->isIterableAtLeastOnce()->yes(); $result = []; foreach ($separatorType->getConstantStrings() as $separator) { foreach ($arrayType->getConstantArrays() as $constantArray) { - $constantType = $this->inferConstantType($constantArray, $separator); + $constantType = $this->inferConstantType($constantArray, $separator, $isNonEmpty); if ($constantType !== null) { $result[] = $constantType; continue; @@ -110,7 +111,7 @@ private function implode(Type $arrayType, Type $separatorType): Type return new StringType(); } - private function inferConstantType(ConstantArrayType $arrayType, ConstantStringType $separatorType): ?Type + private function inferConstantType(ConstantArrayType $arrayType, ConstantStringType $separatorType, bool $isNonEmpty): ?Type { $sep = $separatorType->getValue(); $valueTypes = $arrayType->getValueTypes(); @@ -150,9 +151,16 @@ private function inferConstantType(ConstantArrayType $arrayType, ConstantStringT $strings = []; foreach ($partials as $partial) { + if ($partial === [] && $isNonEmpty) { + continue; + } $strings[] = new ConstantStringType(implode($sep, $partial)); } + if ($strings === []) { + return null; + } + return TypeCombinator::union(...$strings); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-14558.php b/tests/PHPStan/Analyser/nsrt/bug-14558.php new file mode 100644 index 0000000000..6f6259d411 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-14558.php @@ -0,0 +1,81 @@ + */ +function get_sort_keys(mixed ...$args): array { return ['a']; } + +function cond(int $i): bool { return true; } + + +// Playground 1: with outer foreach loop +function test1(): void +{ + $cols_cat = [ ]; + + foreach ([ 'PrV', 'PrA', 'Acc' ] as $g) { + $num_types = num_types($g); + for ($i = 1; $i <= $num_types; $i++) { + + if (cond($i)) { + + $k = 0; + $tmp_sort_keys = [ ]; + foreach (get_sort_keys($g, $i) as $ce_tri) { + $k++; + $tmp_sort_alias = "Tri{$k}_Cat_{$g}{$i}"; + $tmp_sort_keys[$tmp_sort_alias] = $tmp_sort_alias; + } + assertType('non-falsy-string', implode(',', $tmp_sort_keys)); + $cols_cat[] = [ + 'g' => $g + , 't' => $i + , 's' => implode(',', $tmp_sort_keys) + ]; + + } + } + + } + + assertType('list, s: non-falsy-string}>', $cols_cat); +} + +// Playground 2: without outer foreach loop +function test2(): void +{ + $cols_cat = [ ]; + + $g = 'PrV'; + $num_types = num_types($g); + for ($i = 1; $i <= $num_types; $i++) { + + if (cond($i)) { + + $k = 0; + $tmp_sort_keys = [ ]; + foreach (get_sort_keys($g, $i) as $ce_tri) { + $k++; + $tmp_sort_alias = "Tri{$k}_Cat_{$g}{$i}"; + $tmp_sort_keys[$tmp_sort_alias] = $tmp_sort_alias; + } + + assertType('non-falsy-string', implode(',', $tmp_sort_keys)); + $cols_cat[] = [ + 'g' => $g + , 't' => $i + , 's' => implode(',', $tmp_sort_keys) + ]; + + } + } + + assertType('list, s: non-falsy-string}>', $cols_cat); +}