Skip to content

Commit 9bbf9b8

Browse files
ondrejmirtesclaude
andcommitted
Skip implode() constant-fold for unsealed constant arrays
`inferConstantType` built the exact result string from the explicit `getValueTypes()` only — `implode(',', array{'a', 'b', ...<int, string>})` inferred `'a,b'` even though the unsealed extras can append further segments. That's an unsound constant fold. Bail out of the constant fold when the input `isUnsealed()->yes()` and fall through to the accessory-based result, which only keeps what's provable (e.g. `non-falsy-string` when the separator is non-falsy and the explicit prefix guarantees at least one separator). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 839a3d6 commit 9bbf9b8

2 files changed

Lines changed: 24 additions & 0 deletions

File tree

src/Type/Php/ImplodeFunctionReturnTypeExtension.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ private function implode(Type $arrayType, Type $separatorType): Type
113113

114114
private function inferConstantType(ConstantArrayType $arrayType, ConstantStringType $separatorType, bool $isNonEmpty): ?Type
115115
{
116+
// Unsealed extras can append further segments the constant fold
117+
// can't see, so the exact string result would be unsound. Fall
118+
// back to the accessory-based result.
119+
if ($arrayType->isUnsealed()->yes()) {
120+
return null;
121+
}
122+
116123
$sep = $separatorType->getValue();
117124
$valueTypes = $arrayType->getValueTypes();
118125
$limit = InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT;

tests/PHPStan/Analyser/nsrt/implode.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,21 @@ public function constArrays6($constArr) {
6868
public function constArrays7($constArr) {
6969
assertType("'1a'|'1b'|'1c'|'2a'|'2b'|'2c'|'a'|'b'|'c'", implode('', $constArr));
7070
}
71+
72+
/** @param array{'a', 'b', ...<int, string>} $unsealed */
73+
public function unsealedConstArr($unsealed) {
74+
// The unsealed `<int, string>` extras can append more segments, so
75+
// the exact constant fold `'a,b'` is unsound. The result keeps only
76+
// what's guaranteed: with a non-falsy separator and at least two
77+
// explicit elements, the output always contains a comma.
78+
assertType('non-falsy-string', implode(',', $unsealed));
79+
}
80+
81+
/** @param array{'a', 'b', ...<int, string>} $unsealed */
82+
public function unsealedConstArrEmptySeparator($unsealed) {
83+
// Empty separator + a possibly-empty unsealed value type leaves no
84+
// accessory PHPStan can prove, so the result widens to `string`
85+
// (still sound — no bogus constant fold).
86+
assertType('string', implode('', $unsealed));
87+
}
7188
}

0 commit comments

Comments
 (0)