Skip to content

Commit f772f3c

Browse files
ondrejmirtesclaude
andcommitted
Compute constant substr() result independently of the runtime PHP version
The is_bool($substr) branch re-checked substrReturnFalseInsteadOfEmptyString(), which the sharper conditional guards proved always-true when analysing on PHP >= 8 (there substr() never returns false, so $substr is only bool via substrOrFalse()). The finding is genuinely version-specific and cannot live in the shared baseline. Route the non-mb_substr path through substrOrFalse(), whose false|string result does not depend on the runtime substr() semantics, and map false to the analysed version's result. Behaviour is unchanged; drops the version-specific baseline entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CGhnJQpUWRRpg6H5LuWJkA
1 parent ae4ff1c commit f772f3c

2 files changed

Lines changed: 17 additions & 31 deletions

File tree

‎phpstan-baseline.neon‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,12 +1566,6 @@ parameters:
15661566
count: 1
15671567
path: src/Type/Php/ReflectionMethodConstructorThrowTypeExtension.php
15681568

1569-
-
1570-
rawMessage: If condition is always true.
1571-
identifier: if.alwaysTrue
1572-
count: 1
1573-
path: src/Type/Php/SubstrDynamicReturnTypeExtension.php
1574-
15751569
-
15761570
rawMessage: 'Doing instanceof PHPStan\Type\ObjectType is error-prone and deprecated. Use Type::isObject() or Type::getObjectClassNames() instead.'
15771571
identifier: phpstanApi.instanceofType

‎src/Type/Php/SubstrDynamicReturnTypeExtension.php‎

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
use PHPStan\Type\UnionType;
2424
use function count;
2525
use function in_array;
26-
use function is_bool;
2726
use function mb_substr;
2827
use function strlen;
2928
use function substr;
@@ -75,32 +74,25 @@ public function getTypeFromFunctionCall(
7574
) {
7675
$results = [];
7776
foreach ($constantStrings as $constantString) {
78-
if ($length !== null) {
79-
if ($functionReflection->getName() === 'mb_substr') {
80-
$substr = mb_substr($constantString->getValue(), $offset->getValue(), $length->getValue());
81-
} elseif ($this->phpVersion->substrReturnFalseInsteadOfEmptyString()) {
82-
$substr = $this->substrOrFalse($constantString->getValue(), $offset->getValue(), $length->getValue());
83-
} else {
84-
$substr = substr($constantString->getValue(), $offset->getValue(), $length->getValue());
85-
}
86-
} else {
87-
if ($functionReflection->getName() === 'mb_substr') {
88-
$substr = mb_substr($constantString->getValue(), $offset->getValue());
89-
} elseif ($this->phpVersion->substrReturnFalseInsteadOfEmptyString()) {
90-
// Simulate substr call on an older PHP version if the runtime one is too new.
91-
$substr = $this->substrOrFalse($constantString->getValue(), $offset->getValue());
92-
} else {
93-
$substr = substr($constantString->getValue(), $offset->getValue());
94-
}
77+
if ($functionReflection->getName() === 'mb_substr') {
78+
$substr = $length !== null
79+
? mb_substr($constantString->getValue(), $offset->getValue(), $length->getValue())
80+
: mb_substr($constantString->getValue(), $offset->getValue());
81+
$results[] = new ConstantStringType($substr);
82+
continue;
9583
}
9684

97-
if (is_bool($substr)) {
98-
if ($this->phpVersion->substrReturnFalseInsteadOfEmptyString()) {
99-
$results[] = new ConstantBooleanType($substr);
100-
} else {
101-
// Simulate substr call on a recent PHP version if the runtime one is too old.
102-
$results[] = new ConstantStringType('');
103-
}
85+
// substrOrFalse() detects an out-of-range offset with its own length
86+
// check, so the result does not depend on the runtime PHP version's
87+
// substr() semantics. false is then mapped to the analysed version's
88+
// result: false on PHP < 8, an empty string on PHP >= 8.
89+
$substr = $length !== null
90+
? $this->substrOrFalse($constantString->getValue(), $offset->getValue(), $length->getValue())
91+
: $this->substrOrFalse($constantString->getValue(), $offset->getValue());
92+
if ($substr === false) {
93+
$results[] = $this->phpVersion->substrReturnFalseInsteadOfEmptyString()
94+
? new ConstantBooleanType(false)
95+
: new ConstantStringType('');
10496
} else {
10597
$results[] = new ConstantStringType($substr);
10698
}

0 commit comments

Comments
 (0)