Skip to content

Commit 697e042

Browse files
phpstan-botstaabmclaude
authored
Fix implode losing non-empty-string when ConstantArrayType has all-optional keys (#5578)
Co-authored-by: staabm <120441+staabm@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 98d015f commit 697e042

2 files changed

Lines changed: 91 additions & 2 deletions

File tree

src/Type/Php/ImplodeFunctionReturnTypeExtension.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,11 @@ public function getTypeFromFunctionCall(
6262
private function implode(Type $arrayType, Type $separatorType): Type
6363
{
6464
if (count($arrayType->getConstantArrays()) > 0 && count($separatorType->getConstantStrings()) > 0) {
65+
$isNonEmpty = $arrayType->isIterableAtLeastOnce()->yes();
6566
$result = [];
6667
foreach ($separatorType->getConstantStrings() as $separator) {
6768
foreach ($arrayType->getConstantArrays() as $constantArray) {
68-
$constantType = $this->inferConstantType($constantArray, $separator);
69+
$constantType = $this->inferConstantType($constantArray, $separator, $isNonEmpty);
6970
if ($constantType !== null) {
7071
$result[] = $constantType;
7172
continue;
@@ -110,7 +111,7 @@ private function implode(Type $arrayType, Type $separatorType): Type
110111
return new StringType();
111112
}
112113

113-
private function inferConstantType(ConstantArrayType $arrayType, ConstantStringType $separatorType): ?Type
114+
private function inferConstantType(ConstantArrayType $arrayType, ConstantStringType $separatorType, bool $isNonEmpty): ?Type
114115
{
115116
$sep = $separatorType->getValue();
116117
$valueTypes = $arrayType->getValueTypes();
@@ -150,9 +151,16 @@ private function inferConstantType(ConstantArrayType $arrayType, ConstantStringT
150151

151152
$strings = [];
152153
foreach ($partials as $partial) {
154+
if ($partial === [] && $isNonEmpty) {
155+
continue;
156+
}
153157
$strings[] = new ConstantStringType(implode($sep, $partial));
154158
}
155159

160+
if ($strings === []) {
161+
return null;
162+
}
163+
156164
return TypeCombinator::union(...$strings);
157165
}
158166

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug14558;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
/** @return non-negative-int */
10+
function num_types(string $g): int { return 0; }
11+
12+
/** @return non-empty-list<non-empty-string> */
13+
function get_sort_keys(mixed ...$args): array { return ['a']; }
14+
15+
function cond(int $i): bool { return true; }
16+
17+
18+
// Playground 1: with outer foreach loop
19+
function test1(): void
20+
{
21+
$cols_cat = [ ];
22+
23+
foreach ([ 'PrV', 'PrA', 'Acc' ] as $g) {
24+
$num_types = num_types($g);
25+
for ($i = 1; $i <= $num_types; $i++) {
26+
27+
if (cond($i)) {
28+
29+
$k = 0;
30+
$tmp_sort_keys = [ ];
31+
foreach (get_sort_keys($g, $i) as $ce_tri) {
32+
$k++;
33+
$tmp_sort_alias = "Tri{$k}_Cat_{$g}{$i}";
34+
$tmp_sort_keys[$tmp_sort_alias] = $tmp_sort_alias;
35+
}
36+
assertType('non-falsy-string', implode(',', $tmp_sort_keys));
37+
$cols_cat[] = [
38+
'g' => $g
39+
, 't' => $i
40+
, 's' => implode(',', $tmp_sort_keys)
41+
];
42+
43+
}
44+
}
45+
46+
}
47+
48+
assertType('list<array{g: \'Acc\'|\'PrA\'|\'PrV\', t: int<1, max>, s: non-falsy-string}>', $cols_cat);
49+
}
50+
51+
// Playground 2: without outer foreach loop
52+
function test2(): void
53+
{
54+
$cols_cat = [ ];
55+
56+
$g = 'PrV';
57+
$num_types = num_types($g);
58+
for ($i = 1; $i <= $num_types; $i++) {
59+
60+
if (cond($i)) {
61+
62+
$k = 0;
63+
$tmp_sort_keys = [ ];
64+
foreach (get_sort_keys($g, $i) as $ce_tri) {
65+
$k++;
66+
$tmp_sort_alias = "Tri{$k}_Cat_{$g}{$i}";
67+
$tmp_sort_keys[$tmp_sort_alias] = $tmp_sort_alias;
68+
}
69+
70+
assertType('non-falsy-string', implode(',', $tmp_sort_keys));
71+
$cols_cat[] = [
72+
'g' => $g
73+
, 't' => $i
74+
, 's' => implode(',', $tmp_sort_keys)
75+
];
76+
77+
}
78+
}
79+
80+
assertType('list<array{g: \'PrV\', t: int<1, max>, s: non-falsy-string}>', $cols_cat);
81+
}

0 commit comments

Comments
 (0)