Skip to content

Commit 11b95a9

Browse files
committed
Improve narrowing after string functions
1 parent 5892e8d commit 11b95a9

File tree

2 files changed

+52
-36
lines changed

2 files changed

+52
-36
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,41 +1200,6 @@ private function specifyTypesForConstantStringBinaryExpression(
12001200
}
12011201
$constantStringValue = $scalarValues[0];
12021202

1203-
if (
1204-
$context->truthy()
1205-
&& $exprNode instanceof FuncCall
1206-
&& $exprNode->name instanceof Name
1207-
&& in_array(strtolower($exprNode->name->toString()), [
1208-
'substr', 'strstr', 'stristr', 'strchr', 'strrchr', 'strtolower', 'strtoupper', 'ucfirst', 'lcfirst',
1209-
'mb_substr', 'mb_strstr', 'mb_stristr', 'mb_strchr', 'mb_strrchr', 'mb_strtolower', 'mb_strtoupper', 'mb_ucfirst', 'mb_lcfirst',
1210-
'ucwords', 'mb_convert_case', 'mb_convert_kana',
1211-
], true)
1212-
&& isset($exprNode->getArgs()[0])
1213-
&& $constantStringValue !== ''
1214-
) {
1215-
$argType = $scope->getType($exprNode->getArgs()[0]->value);
1216-
1217-
if ($argType->isString()->yes()) {
1218-
if ($constantStringValue !== '0') {
1219-
return $this->create(
1220-
$exprNode->getArgs()[0]->value,
1221-
TypeCombinator::intersect($argType, new AccessoryNonFalsyStringType()),
1222-
$context,
1223-
false,
1224-
$scope,
1225-
);
1226-
}
1227-
1228-
return $this->create(
1229-
$exprNode->getArgs()[0]->value,
1230-
TypeCombinator::intersect($argType, new AccessoryNonEmptyStringType()),
1231-
$context,
1232-
false,
1233-
$scope,
1234-
);
1235-
}
1236-
}
1237-
12381203
if (
12391204
$exprNode instanceof FuncCall
12401205
&& $exprNode->name instanceof Name
@@ -2171,6 +2136,41 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
21712136
}
21722137
}
21732138

2139+
if (
2140+
$context->truthy()
2141+
&& $unwrappedLeftExpr instanceof FuncCall
2142+
&& $unwrappedLeftExpr->name instanceof Name
2143+
&& in_array(strtolower($unwrappedLeftExpr->name->toString()), [
2144+
'substr', 'strstr', 'stristr', 'strchr', 'strrchr', 'strtolower', 'strtoupper', 'ucfirst', 'lcfirst',
2145+
'mb_substr', 'mb_strstr', 'mb_stristr', 'mb_strchr', 'mb_strrchr', 'mb_strtolower', 'mb_strtoupper', 'mb_ucfirst', 'mb_lcfirst',
2146+
'ucwords', 'mb_convert_case', 'mb_convert_kana',
2147+
], true)
2148+
&& isset($unwrappedLeftExpr->getArgs()[0])
2149+
&& $rightType->isNonEmptyString()->yes()
2150+
) {
2151+
$argType = $scope->getType($unwrappedLeftExpr->getArgs()[0]->value);
2152+
2153+
if ($argType->isString()->yes()) {
2154+
if ($rightType->isNonFalsyString()->yes()) {
2155+
return $this->create(
2156+
$unwrappedLeftExpr->getArgs()[0]->value,
2157+
TypeCombinator::intersect($argType, new AccessoryNonFalsyStringType()),
2158+
$context,
2159+
false,
2160+
$scope,
2161+
);
2162+
}
2163+
2164+
return $this->create(
2165+
$unwrappedLeftExpr->getArgs()[0]->value,
2166+
TypeCombinator::intersect($argType, new AccessoryNonEmptyStringType()),
2167+
$context,
2168+
false,
2169+
$scope,
2170+
);
2171+
}
2172+
}
2173+
21742174
if ($rightType->isInteger()->yes() || $rightType->isString()->yes()) {
21752175
$types = null;
21762176
foreach ($rightType->getFiniteTypes() as $finiteType) {

tests/PHPStan/Analyser/nsrt/non-empty-string-substr-specifying.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
use function PHPStan\Testing\assertType;
66

7-
class Foo {
7+
class Foo
8+
{
89
public function nonEmptySubstr(string $s, int $offset, int $length): void
910
{
1011
if (substr($s, 10) === 'hallo') {
@@ -81,4 +82,19 @@ public function nonEmptySubstr(string $s, int $offset, int $length): void
8182
assertType('\'hallo\'', $x);
8283
}
8384
}
85+
86+
/**
87+
* @param non-empty-string $nonES
88+
* @param non-falsy-string $falsyString
89+
*/
90+
public function stringTypes(string $s, $nonES, $falsyString): void
91+
{
92+
if (substr($s, 10) === $nonES) {
93+
assertType('non-empty-string', $s);
94+
}
95+
96+
if (substr($s, 10) === $falsyString) {
97+
assertType('non-falsy-string', $s);
98+
}
99+
}
84100
}

0 commit comments

Comments
 (0)