Skip to content

Commit 3e5195b

Browse files
staabmondrejmirtes
authored andcommitted
Support IntegerRangeType in ConstantStringType offset-value-type handling
1 parent 06ab320 commit 3e5195b

File tree

3 files changed

+61
-9
lines changed

3 files changed

+61
-9
lines changed

src/Type/Constant/ConstantStringType.php

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -345,23 +345,43 @@ public function isLiteralString(): TrinaryLogic
345345

346346
public function hasOffsetValueType(Type $offsetType): TrinaryLogic
347347
{
348-
if ($offsetType instanceof ConstantIntegerType) {
349-
return TrinaryLogic::createFromBoolean(
350-
$offsetType->getValue() < strlen($this->value),
351-
);
348+
if ($offsetType->isInteger()->yes()) {
349+
$strLenType = IntegerRangeType::fromInterval(0, strlen($this->value) - 1);
350+
return $strLenType->isSuperTypeOf($offsetType);
352351
}
353352

354353
return parent::hasOffsetValueType($offsetType);
355354
}
356355

357356
public function getOffsetValueType(Type $offsetType): Type
358357
{
359-
if ($offsetType instanceof ConstantIntegerType) {
360-
if ($offsetType->getValue() < strlen($this->value)) {
361-
return new self($this->value[$offsetType->getValue()]);
358+
if ($offsetType->isInteger()->yes()) {
359+
if ($offsetType instanceof ConstantIntegerType) {
360+
if ($offsetType->getValue() < strlen($this->value)) {
361+
return new self($this->value[$offsetType->getValue()]);
362+
}
363+
364+
return new ErrorType();
362365
}
363366

364-
return new ErrorType();
367+
$strLenType = IntegerRangeType::fromInterval(0, strlen($this->value) - 1);
368+
$intersected = TypeCombinator::intersect($strLenType, $offsetType);
369+
if ($intersected instanceof IntegerRangeType) {
370+
$finiteTypes = $intersected->getFiniteTypes();
371+
if ($finiteTypes === []) {
372+
return parent::getOffsetValueType($offsetType);
373+
}
374+
375+
$chars = [];
376+
foreach ($finiteTypes as $constantInteger) {
377+
$chars[] = new self($this->value[$constantInteger->getValue()]);
378+
}
379+
if (!$strLenType->isSuperTypeOf($offsetType)->yes()) {
380+
$chars[] = new self('');
381+
}
382+
383+
return TypeCombinator::union(...$chars);
384+
}
365385
}
366386

367387
return parent::getOffsetValueType($offsetType);

tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2904,7 +2904,7 @@ public function dataBinaryOperations(): array
29042904
'$fooString[4]',
29052905
],
29062906
[
2907-
'non-empty-string',
2907+
"''|'f'|'o'",
29082908
'$fooString[$integer]',
29092909
],
29102910
[
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace StringOffsets;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
/**
8+
* @param int<1, 3> $oneToThree
9+
* @param int<3, 10> $threeToTen
10+
* @param int<10, max> $tenOrMore
11+
* @param int<-10, -5> $negative
12+
*
13+
* @return void
14+
*/
15+
function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $i) {
16+
$s = "world";
17+
if (rand(0, 1)) {
18+
$s = "hello";
19+
}
20+
21+
assertType("''|'d'|'e'|'h'|'l'|'o'|'r'|'w'", $s[$i]);
22+
23+
assertType("'h'|'w'", $s[0]);
24+
25+
assertType("'e'|'l'|'o'|'r'", $s[$oneToThree]);
26+
assertType('*ERROR*', $s[$tenOrMore]);
27+
assertType("''|'d'|'l'|'o'", $s[$threeToTen]);
28+
assertType("*ERROR*", $s[$negative]);
29+
30+
$longString = "myF5HnJv799kWf8VRI7g97vwnABTwN9y2CzAVELCBfRqyqkdTzXg7BkGXcwuIOscAiT6tSuJGzVZOJnYXvkiKQzYBNjjkCPOzSKXR5YHRlVxV1BetqZz4XOmaH9mtacJ9azNYL6bNXezSBjX13BSZy02SK2udzQLbTPNQwlKadKaNkUxjtWegkb8QDFaXbzH1JENVSLVH0FYd6POBU82X1xu7FDDKYLzwsWJHBGVhG8iugjEGwLj22x5ViosUyKR";
31+
assertType("non-empty-string", $longString[$i]);
32+
}

0 commit comments

Comments
 (0)