Skip to content

Commit 6b4f063

Browse files
Resolve substr based on configured PHP version
1 parent 6e87a98 commit 6b4f063

File tree

8 files changed

+62
-10
lines changed

8 files changed

+62
-10
lines changed

build/baseline-8.0.neon

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,3 @@ parameters:
2929
message: "#^Strict comparison using \\=\\=\\= between list<string> and false will always evaluate to false\\.$#"
3030
count: 1
3131
path: ../src/Type/Php/StrSplitFunctionReturnTypeExtension.php
32-
33-
-
34-
message: "#^Call to function is_bool\\(\\) with string will always evaluate to false\\.$#"
35-
count: 1
36-
path: ../src/Type/Php/SubstrDynamicReturnTypeExtension.php

src/Type/Php/SubstrDynamicReturnTypeExtension.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use function in_array;
2424
use function is_bool;
2525
use function mb_substr;
26+
use function strlen;
2627
use function substr;
2728

2829
final class SubstrDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
@@ -74,19 +75,29 @@ public function getTypeFromFunctionCall(
7475
if ($length !== null) {
7576
if ($functionReflection->getName() === 'mb_substr') {
7677
$substr = mb_substr($constantString->getValue(), $offset->getValue(), $length->getValue());
78+
} elseif ($this->phpVersion->substrReturnFalseInsteadOfEmptyString()) {
79+
$substr = $this->substrOrFalse($constantString->getValue(), $offset->getValue(), $length->getValue());
7780
} else {
7881
$substr = substr($constantString->getValue(), $offset->getValue(), $length->getValue());
7982
}
8083
} else {
8184
if ($functionReflection->getName() === 'mb_substr') {
8285
$substr = mb_substr($constantString->getValue(), $offset->getValue());
86+
} elseif ($this->phpVersion->substrReturnFalseInsteadOfEmptyString()) {
87+
// Simulate substr call on an older PHP version if the runtime one is too new.
88+
$substr = $this->substrOrFalse($constantString->getValue(), $offset->getValue());
8389
} else {
8490
$substr = substr($constantString->getValue(), $offset->getValue());
8591
}
8692
}
8793

8894
if (is_bool($substr)) {
89-
$results[] = new ConstantBooleanType($substr);
95+
if ($this->phpVersion->substrReturnFalseInsteadOfEmptyString()) {
96+
$results[] = new ConstantBooleanType($substr);
97+
} else {
98+
// Simulate substr call on a recent PHP version if the runtime one is too old.
99+
$results[] = new ConstantStringType('');
100+
}
90101
} else {
91102
$results[] = new ConstantStringType($substr);
92103
}
@@ -127,4 +138,28 @@ public function getTypeFromFunctionCall(
127138
return null;
128139
}
129140

141+
private function substrOrFalse(string $string, int $offset, ?int $length = null): false|string
142+
{
143+
$strlen = strlen($string);
144+
145+
if ($offset > $strlen) {
146+
return false;
147+
}
148+
149+
if ($length !== null && $length < 0) {
150+
if ($offset < 0 && -$length > $strlen) {
151+
return false;
152+
}
153+
if ($offset >= 0 && -$length > $strlen - $offset) {
154+
return false;
155+
}
156+
}
157+
158+
if ($length === null) {
159+
return substr($string, $offset);
160+
}
161+
162+
return substr($string, $offset, $length);
163+
}
164+
130165
}

tests/PHPStan/Analyser/LooseConstComparisonPhp7Test.php renamed to tests/PHPStan/Analyser/NodeScopeResolverPhp7Test.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use PHPStan\Testing\TypeInferenceTestCase;
66

7-
class LooseConstComparisonPhp7Test extends TypeInferenceTestCase
7+
class NodeScopeResolverPhp7Test extends TypeInferenceTestCase
88
{
99

1010
/**
@@ -15,6 +15,8 @@ public function dataFileAsserts(): iterable
1515
// compares constants according to the php-version phpstan configuration,
1616
// _NOT_ the current php runtime version
1717
yield from $this->gatherAssertTypes(__DIR__ . '/data/loose-const-comparison-php7.php');
18+
19+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-13129-php7.php');
1820
}
1921

2022
/**
@@ -33,7 +35,7 @@ public function testFileAsserts(
3335
public static function getAdditionalConfigFiles(): array
3436
{
3537
return [
36-
__DIR__ . '/looseConstComparisonPhp7.neon',
38+
__DIR__ . '/nodeScopeResolverPhp7.neon',
3739
];
3840
}
3941

tests/PHPStan/Analyser/LooseConstComparisonPhp8Test.php renamed to tests/PHPStan/Analyser/NodeScopeResolverPhp8Test.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use PHPStan\Testing\TypeInferenceTestCase;
66

7-
class LooseConstComparisonPhp8Test extends TypeInferenceTestCase
7+
class NodeScopeResolverPhp8Test extends TypeInferenceTestCase
88
{
99

1010
/**
@@ -15,6 +15,8 @@ public function dataFileAsserts(): iterable
1515
// compares constants according to the php-version phpstan configuration,
1616
// _NOT_ the current php runtime version
1717
yield from $this->gatherAssertTypes(__DIR__ . '/data/loose-const-comparison-php8.php');
18+
19+
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-13129-php8.php');
1820
}
1921

2022
/**
@@ -33,7 +35,7 @@ public function testFileAsserts(
3335
public static function getAdditionalConfigFiles(): array
3436
{
3537
return [
36-
__DIR__ . '/looseConstComparisonPhp8.neon',
38+
__DIR__ . '/nodeScopeResolverPhp8.neon',
3739
];
3840
}
3941

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Bug13129PHP7;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
assertType("false", substr("test", 5));
8+
assertType("false", substr("test", -1, -5));
9+
assertType("false", substr("test", 1, -4));
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Bug13129PHP8;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
assertType("''", substr("test", 5));
8+
assertType("''", substr("test", -1, -5));
9+
assertType("''", substr("test", 1, -4));

0 commit comments

Comments
 (0)