Skip to content

Commit 26f5957

Browse files
authored
Merge branch refs/heads/1.12.x into 2.0.x
2 parents d547ddc + 3e5195b commit 26f5957

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
@@ -343,23 +343,43 @@ public function isLiteralString(): TrinaryLogic
343343

344344
public function hasOffsetValueType(Type $offsetType): TrinaryLogic
345345
{
346-
if ($offsetType instanceof ConstantIntegerType) {
347-
return TrinaryLogic::createFromBoolean(
348-
$offsetType->getValue() < strlen($this->value),
349-
);
346+
if ($offsetType->isInteger()->yes()) {
347+
$strLenType = IntegerRangeType::fromInterval(0, strlen($this->value) - 1);
348+
return $strLenType->isSuperTypeOf($offsetType);
350349
}
351350

352351
return parent::hasOffsetValueType($offsetType);
353352
}
354353

355354
public function getOffsetValueType(Type $offsetType): Type
356355
{
357-
if ($offsetType instanceof ConstantIntegerType) {
358-
if ($offsetType->getValue() < strlen($this->value)) {
359-
return new self($this->value[$offsetType->getValue()]);
356+
if ($offsetType->isInteger()->yes()) {
357+
if ($offsetType instanceof ConstantIntegerType) {
358+
if ($offsetType->getValue() < strlen($this->value)) {
359+
return new self($this->value[$offsetType->getValue()]);
360+
}
361+
362+
return new ErrorType();
360363
}
361364

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

365385
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)