Skip to content

Commit b4b0887

Browse files
authored
Improve explode return type precision
1 parent 39f5f45 commit b4b0887

File tree

4 files changed

+25
-24
lines changed

4 files changed

+25
-24
lines changed

src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Php\PhpVersion;
88
use PHPStan\Reflection\FunctionReflection;
9-
use PHPStan\Reflection\ParametersAcceptorSelector;
109
use PHPStan\Type\Accessory\AccessoryArrayListType;
1110
use PHPStan\Type\Accessory\NonEmptyArrayType;
1211
use PHPStan\Type\ArrayType;
@@ -41,32 +40,34 @@ public function getTypeFromFunctionCall(
4140
Scope $scope,
4241
): ?Type
4342
{
44-
if (count($functionCall->getArgs()) < 2) {
43+
$args = $functionCall->getArgs();
44+
if (count($args) < 2) {
4545
return null;
4646
}
4747

48-
$delimiterType = $scope->getType($functionCall->getArgs()[0]->value);
49-
$isSuperset = (new ConstantStringType(''))->isSuperTypeOf($delimiterType);
50-
if ($isSuperset->yes()) {
51-
if ($this->phpVersion->getVersionId() >= 80000) {
48+
$delimiterType = $scope->getType($args[0]->value);
49+
$isEmptyString = (new ConstantStringType(''))->isSuperTypeOf($delimiterType);
50+
if ($isEmptyString->yes()) {
51+
if ($this->phpVersion->throwsTypeErrorForInternalFunctions()) {
5252
return new NeverType();
5353
}
5454
return new ConstantBooleanType(false);
55-
} elseif ($isSuperset->no()) {
56-
$arrayType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new StringType()));
57-
if (
58-
!isset($functionCall->getArgs()[2])
59-
|| IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($scope->getType($functionCall->getArgs()[2]->value))->yes()
60-
) {
61-
return TypeCombinator::intersect($arrayType, new NonEmptyArrayType());
62-
}
55+
}
56+
57+
$returnType = AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new StringType()));
58+
if (
59+
!isset($args[2])
60+
|| IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($scope->getType($args[2]->value))->yes()
61+
) {
62+
$returnType = TypeCombinator::intersect($returnType, new NonEmptyArrayType());
63+
}
6364

64-
return $arrayType;
65+
if (!$this->phpVersion->throwsValueErrorForInternalFunctions() && $isEmptyString->maybe()) {
66+
$returnType = TypeCombinator::union($returnType, new ConstantBooleanType(false));
6567
}
6668

67-
$returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
6869
if ($delimiterType instanceof MixedType) {
69-
return TypeUtils::toBenevolentUnion($returnType);
70+
$returnType = TypeUtils::toBenevolentUnion($returnType);
7071
}
7172

7273
return $returnType;

tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7292,15 +7292,15 @@ public function dataExplode(): array
72927292
'$sureFalse',
72937293
],
72947294
[
7295-
PHP_VERSION_ID < 80000 ? 'list<string>|false' : 'list<string>',
7295+
PHP_VERSION_ID < 80000 ? 'non-empty-list<string>|false' : 'non-empty-list<string>',
72967296
'$arrayOrFalse',
72977297
],
72987298
[
7299-
PHP_VERSION_ID < 80000 ? 'list<string>|false' : 'list<string>',
7299+
PHP_VERSION_ID < 80000 ? 'non-empty-list<string>|false' : 'non-empty-list<string>',
73007300
'$anotherArrayOrFalse',
73017301
],
73027302
[
7303-
PHP_VERSION_ID < 80000 ? '(list<string>|false)' : 'list<string>',
7303+
PHP_VERSION_ID < 80000 ? '(non-empty-list<string>|false)' : 'non-empty-list<string>',
73047304
'$benevolentArrayOrFalse',
73057305
],
73067306
];

tests/PHPStan/Analyser/data/bug-3961-php8.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ public function doFoo(string $v, string $d, $m): void
1414
assertType('list<string>', explode('.', $v, -2));
1515
assertType('non-empty-list<string>', explode('.', $v, 0));
1616
assertType('non-empty-list<string>', explode('.', $v, 1));
17-
assertType('list<string>', explode($d, $v));
18-
assertType('list<string>', explode($m, $v));
17+
assertType('non-empty-list<string>', explode($d, $v));
18+
assertType('non-empty-list<string>', explode($m, $v));
1919
}
2020

2121
}

tests/PHPStan/Analyser/data/bug-3961.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ public function doFoo(string $v, string $d, $m): void
1414
assertType('list<string>', explode('.', $v, -2));
1515
assertType('non-empty-list<string>', explode('.', $v, 0));
1616
assertType('non-empty-list<string>', explode('.', $v, 1));
17-
assertType('list<string>|false', explode($d, $v));
18-
assertType('(list<string>|false)', explode($m, $v));
17+
assertType('non-empty-list<string>|false', explode($d, $v));
18+
assertType('(non-empty-list<string>|false)', explode($m, $v));
1919
}
2020

2121
}

0 commit comments

Comments
 (0)