Skip to content

Commit 9ebc5e5

Browse files
committed
Fix negative offset false positive on constant string
1 parent eded2c3 commit 9ebc5e5

File tree

2 files changed

+20
-5
lines changed

2 files changed

+20
-5
lines changed

src/Type/Constant/ConstantStringType.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ public function isUppercaseString(): TrinaryLogic
366366
public function hasOffsetValueType(Type $offsetType): TrinaryLogic
367367
{
368368
if ($offsetType->isInteger()->yes()) {
369-
$strLenType = IntegerRangeType::fromInterval(0, strlen($this->value) - 1);
369+
$strLenType = IntegerRangeType::fromInterval(-strlen($this->value), strlen($this->value) - 1);
370370
return $strLenType->isSuperTypeOf($offsetType);
371371
}
372372

@@ -376,15 +376,16 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic
376376
public function getOffsetValueType(Type $offsetType): Type
377377
{
378378
if ($offsetType->isInteger()->yes()) {
379+
$strLenType = IntegerRangeType::fromInterval(-strlen($this->value), strlen($this->value) - 1);
380+
379381
if ($offsetType instanceof ConstantIntegerType) {
380-
if ($offsetType->getValue() < strlen($this->value)) {
382+
if ($strLenType->isSuperTypeOf($offsetType)->yes()) {
381383
return new self($this->value[$offsetType->getValue()]);
382384
}
383385

384386
return new ErrorType();
385387
}
386388

387-
$strLenType = IntegerRangeType::fromInterval(0, strlen($this->value) - 1);
388389
$intersected = TypeCombinator::intersect($strLenType, $offsetType);
389390
if ($intersected instanceof IntegerRangeType) {
390391
$finiteTypes = $intersected->getFiniteTypes();

tests/PHPStan/Analyser/nsrt/string-offsets.php

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99
* @param int<3, 10> $threeToTen
1010
* @param int<10, max> $tenOrMore
1111
* @param int<-10, -5> $negative
12+
* @param int<min, -6> $smallerMinusSix
1213
* @param lowercase-string $lowercase
1314
*
1415
* @return void
1516
*/
16-
function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $i, string $lowercase) {
17+
function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $smallerMinusSix, int $i, string $lowercase) {
1718
$s = "world";
1819
if (rand(0, 1)) {
1920
$s = "hello";
@@ -26,10 +27,23 @@ function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $i, string $
2627
assertType("'e'|'l'|'o'|'r'", $s[$oneToThree]);
2728
assertType('*ERROR*', $s[$tenOrMore]);
2829
assertType("''|'d'|'l'|'o'", $s[$threeToTen]);
29-
assertType("*ERROR*", $s[$negative]);
30+
assertType("non-empty-string", $s[$negative]);
31+
assertType("*ERROR*", $s[$smallerMinusSix]);
3032

3133
$longString = "myF5HnJv799kWf8VRI7g97vwnABTwN9y2CzAVELCBfRqyqkdTzXg7BkGXcwuIOscAiT6tSuJGzVZOJnYXvkiKQzYBNjjkCPOzSKXR5YHRlVxV1BetqZz4XOmaH9mtacJ9azNYL6bNXezSBjX13BSZy02SK2udzQLbTPNQwlKadKaNkUxjtWegkb8QDFaXbzH1JENVSLVH0FYd6POBU82X1xu7FDDKYLzwsWJHBGVhG8iugjEGwLj22x5ViosUyKR";
3234
assertType("non-empty-string", $longString[$i]);
3335

3436
assertType("lowercase-string&non-empty-string", $lowercase[$i]);
3537
}
38+
39+
function bug12122()
40+
{
41+
// see https://3v4l.org/8EMdX
42+
$foo = 'fo';
43+
assertType('*ERROR*', $foo[2]);
44+
assertType("'o'", $foo[1]);
45+
assertType("'f'", $foo[0]);
46+
assertType("'o'", $foo[-1]);
47+
assertType("'f'", $foo[-2]);
48+
assertType('*ERROR*', $foo[-3]);
49+
}

0 commit comments

Comments
 (0)