Skip to content

Commit 1c2bb50

Browse files
Resolve substr based on configured PHP version
1 parent 695e718 commit 1c2bb50

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
@@ -24,6 +24,7 @@
2424
use function in_array;
2525
use function is_bool;
2626
use function mb_substr;
27+
use function strlen;
2728
use function substr;
2829

2930
#[AutowiredService]
@@ -76,19 +77,29 @@ public function getTypeFromFunctionCall(
7677
if ($length !== null) {
7778
if ($functionReflection->getName() === 'mb_substr') {
7879
$substr = mb_substr($constantString->getValue(), $offset->getValue(), $length->getValue());
80+
} elseif ($this->phpVersion->substrReturnFalseInsteadOfEmptyString()) {
81+
$substr = $this->substrOrFalse($constantString->getValue(), $offset->getValue(), $length->getValue());
7982
} else {
8083
$substr = substr($constantString->getValue(), $offset->getValue(), $length->getValue());
8184
}
8285
} else {
8386
if ($functionReflection->getName() === 'mb_substr') {
8487
$substr = mb_substr($constantString->getValue(), $offset->getValue());
88+
} elseif ($this->phpVersion->substrReturnFalseInsteadOfEmptyString()) {
89+
// Simulate substr call on an older PHP version if the runtime one is too new.
90+
$substr = $this->substrOrFalse($constantString->getValue(), $offset->getValue());
8591
} else {
8692
$substr = substr($constantString->getValue(), $offset->getValue());
8793
}
8894
}
8995

9096
if (is_bool($substr)) {
91-
$results[] = new ConstantBooleanType($substr);
97+
if ($this->phpVersion->substrReturnFalseInsteadOfEmptyString()) {
98+
$results[] = new ConstantBooleanType($substr);
99+
} else {
100+
// Simulate substr call on a recent PHP version if the runtime one is too old.
101+
$results[] = new ConstantStringType('');
102+
}
92103
} else {
93104
$results[] = new ConstantStringType($substr);
94105
}
@@ -129,4 +140,28 @@ public function getTypeFromFunctionCall(
129140
return null;
130141
}
131142

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

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
@@ -5,7 +5,7 @@
55
use PHPStan\Testing\TypeInferenceTestCase;
66
use PHPUnit\Framework\Attributes\DataProvider;
77

8-
class LooseConstComparisonPhp7Test extends TypeInferenceTestCase
8+
class NodeScopeResolverPhp7Test extends TypeInferenceTestCase
99
{
1010

1111
/**
@@ -16,6 +16,8 @@ public static function dataFileAsserts(): iterable
1616
// compares constants according to the php-version phpstan configuration,
1717
// _NOT_ the current php runtime version
1818
yield from self::gatherAssertTypes(__DIR__ . '/data/loose-const-comparison-php7.php');
19+
20+
yield from self::gatherAssertTypes(__DIR__ . '/data/bug-13129-php7.php');
1921
}
2022

2123
/**
@@ -34,7 +36,7 @@ public function testFileAsserts(
3436
public static function getAdditionalConfigFiles(): array
3537
{
3638
return [
37-
__DIR__ . '/looseConstComparisonPhp7.neon',
39+
__DIR__ . '/nodeScopeResolverPhp7.neon',
3840
];
3941
}
4042

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
@@ -5,7 +5,7 @@
55
use PHPStan\Testing\TypeInferenceTestCase;
66
use PHPUnit\Framework\Attributes\DataProvider;
77

8-
class LooseConstComparisonPhp8Test extends TypeInferenceTestCase
8+
class NodeScopeResolverPhp8Test extends TypeInferenceTestCase
99
{
1010

1111
/**
@@ -16,6 +16,8 @@ public static function dataFileAsserts(): iterable
1616
// compares constants according to the php-version phpstan configuration,
1717
// _NOT_ the current php runtime version
1818
yield from self::gatherAssertTypes(__DIR__ . '/data/loose-const-comparison-php8.php');
19+
20+
yield from self::gatherAssertTypes(__DIR__ . '/data/bug-13129-php8.php');
1921
}
2022

2123
/**
@@ -34,7 +36,7 @@ public function testFileAsserts(
3436
public static function getAdditionalConfigFiles(): array
3537
{
3638
return [
37-
__DIR__ . '/looseConstComparisonPhp8.neon',
39+
__DIR__ . '/nodeScopeResolverPhp8.neon',
3840
];
3941
}
4042

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)