Skip to content

Commit ae67ac8

Browse files
authored
Handle false return in substr with PHP < 8
1 parent 3e69c48 commit ae67ac8

File tree

7 files changed

+106
-25
lines changed

7 files changed

+106
-25
lines changed

src/Type/Php/SubstrDynamicReturnTypeExtension.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public function getTypeFromFunctionCall(
2727
FunctionReflection $functionReflection,
2828
FuncCall $functionCall,
2929
Scope $scope,
30-
): Type
30+
): ?Type
3131
{
3232
$args = $functionCall->getArgs();
3333
if (count($args) === 0) {
@@ -55,7 +55,7 @@ public function getTypeFromFunctionCall(
5555
}
5656
}
5757

58-
return new StringType();
58+
return null;
5959
}
6060

6161
}

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,8 +475,15 @@ public function dataFileAsserts(): iterable
475475
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-5219.php');
476476
yield from $this->gatherAssertTypes(__DIR__ . '/data/strval.php');
477477
yield from $this->gatherAssertTypes(__DIR__ . '/data/array-next.php');
478+
478479
yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-string.php');
479480
yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-string-replace-functions.php');
481+
if (PHP_VERSION_ID >= 80000) {
482+
yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-string-substr.php');
483+
} else {
484+
yield from $this->gatherAssertTypes(__DIR__ . '/data/non-empty-string-substr-pre-80.php');
485+
}
486+
480487
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3981.php');
481488
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-4711.php');
482489
yield from $this->gatherAssertTypes(__DIR__ . '/data/sscanf.php');
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace NonEmptyStringSubstr;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
/**
11+
* @param non-empty-string $nonEmpty
12+
* @param positive-int $positiveInt
13+
* @param 1|2|3 $postiveRange
14+
* @param -1|-2|-3 $negativeRange
15+
*/
16+
public function doSubstr(string $s, $nonEmpty, $positiveInt, $postiveRange, $negativeRange): void
17+
{
18+
assertType('(string|false)', substr($s, 5));
19+
20+
assertType('(string|false)', substr($s, -5));
21+
assertType('non-empty-string', substr($nonEmpty, -5));
22+
assertType('non-empty-string', substr($nonEmpty, $negativeRange));
23+
24+
assertType('(string|false)', substr($s, 0, 5));
25+
assertType('non-empty-string', substr($nonEmpty, 0, 5));
26+
assertType('non-empty-string', substr($nonEmpty, 0, $postiveRange));
27+
28+
assertType('(string|false)', substr($nonEmpty, 0, -5));
29+
30+
assertType('(string|false)', substr($s, 0, $positiveInt));
31+
assertType('non-empty-string', substr($nonEmpty, 0, $positiveInt));
32+
}
33+
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace NonEmptyStringSubstr;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
/**
11+
* @param non-empty-string $nonEmpty
12+
* @param positive-int $positiveInt
13+
* @param 1|2|3 $postiveRange
14+
* @param -1|-2|-3 $negativeRange
15+
*/
16+
public function doSubstr(string $s, $nonEmpty, $positiveInt, $postiveRange, $negativeRange): void
17+
{
18+
assertType('string', substr($s, 5));
19+
20+
assertType('string', substr($s, -5));
21+
assertType('non-empty-string', substr($nonEmpty, -5));
22+
assertType('non-empty-string', substr($nonEmpty, $negativeRange));
23+
24+
assertType('string', substr($s, 0, 5));
25+
assertType('non-empty-string', substr($nonEmpty, 0, 5));
26+
assertType('non-empty-string', substr($nonEmpty, 0, $postiveRange));
27+
28+
assertType('string', substr($nonEmpty, 0, -5));
29+
30+
assertType('string', substr($s, 0, $positiveInt));
31+
assertType('non-empty-string', substr($nonEmpty, 0, $positiveInt));
32+
}
33+
34+
}

tests/PHPStan/Analyser/data/non-empty-string.php

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -134,29 +134,6 @@ public function doEmpty2(string $s): void
134134
}
135135
}
136136

137-
/**
138-
* @param non-empty-string $nonEmpty
139-
* @param positive-int $positiveInt
140-
* @param 1|2|3 $postiveRange
141-
* @param -1|-2|-3 $negativeRange
142-
*/
143-
public function doSubstr(string $s, $nonEmpty, $positiveInt, $postiveRange, $negativeRange): void
144-
{
145-
assertType('string', substr($s, 5));
146-
147-
assertType('string', substr($s, -5));
148-
assertType('non-empty-string', substr($nonEmpty, -5));
149-
assertType('non-empty-string', substr($nonEmpty, $negativeRange));
150-
151-
assertType('string', substr($s, 0, 5));
152-
assertType('non-empty-string', substr($nonEmpty, 0, 5));
153-
assertType('non-empty-string', substr($nonEmpty, 0, $postiveRange));
154-
155-
assertType('string', substr($nonEmpty, 0, -5));
156-
157-
assertType('string', substr($s, 0, $positiveInt));
158-
assertType('non-empty-string', substr($nonEmpty, 0, $positiveInt));
159-
}
160137
}
161138

162139
class ImplodingStrings

tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,4 +503,21 @@ public function testBug5362(): void
503503
]);
504504
}
505505

506+
public function testBug6939(): void
507+
{
508+
$this->checkAlwaysTrueStrictComparison = true;
509+
510+
if (PHP_VERSION_ID < 80000) {
511+
$this->analyse([__DIR__ . '/data/bug-6939.php'], []);
512+
return;
513+
}
514+
515+
$this->analyse([__DIR__ . '/data/bug-6939.php'], [
516+
[
517+
'Strict comparison using === between string and false will always evaluate to false.',
518+
10,
519+
],
520+
]);
521+
}
522+
506523
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug6939;
4+
5+
class HelloWorld
6+
{
7+
public static function read(int $pos, int $bytes): string
8+
{
9+
$data = substr('', $pos, $bytes);
10+
return $data === false ? '' : $data;
11+
}
12+
}

0 commit comments

Comments
 (0)