Skip to content

Commit 0069107

Browse files
committed
explode() returns a non-empty array in certain scenarios
1 parent 7219419 commit 0069107

File tree

3 files changed

+39
-1
lines changed

3 files changed

+39
-1
lines changed

src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php

+12-1
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Reflection\FunctionReflection;
88
use PHPStan\Reflection\ParametersAcceptorSelector;
9+
use PHPStan\Type\Accessory\NonEmptyArrayType;
910
use PHPStan\Type\ArrayType;
1011
use PHPStan\Type\Constant\ConstantBooleanType;
1112
use PHPStan\Type\Constant\ConstantStringType;
13+
use PHPStan\Type\IntegerRangeType;
1214
use PHPStan\Type\IntegerType;
1315
use PHPStan\Type\MixedType;
1416
use PHPStan\Type\StringType;
1517
use PHPStan\Type\Type;
18+
use PHPStan\Type\TypeCombinator;
1619
use PHPStan\Type\TypeUtils;
1720

1821
class ExplodeFunctionDynamicReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension
@@ -38,7 +41,15 @@ public function getTypeFromFunctionCall(
3841
if ($isSuperset->yes()) {
3942
return new ConstantBooleanType(false);
4043
} elseif ($isSuperset->no()) {
41-
return new ArrayType(new IntegerType(), new StringType());
44+
$arrayType = new ArrayType(new IntegerType(), new StringType());
45+
if (
46+
!isset($functionCall->args[2])
47+
|| IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($scope->getType($functionCall->args[2]->value))->yes()
48+
) {
49+
return TypeCombinator::intersect($arrayType, new NonEmptyArrayType());
50+
}
51+
52+
return $arrayType;
4253
}
4354

4455
$returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();

tests/PHPStan/Analyser/NodeScopeResolverTest.php

+6
Original file line numberDiff line numberDiff line change
@@ -10185,6 +10185,11 @@ public function dataClassConstantOnExpression(): array
1018510185
return $this->gatherAssertTypes(__DIR__ . '/data/class-constant-on-expr.php');
1018610186
}
1018710187

10188+
public function dataBug3961(): array
10189+
{
10190+
return $this->gatherAssertTypes(__DIR__ . '/data/bug-3961.php');
10191+
}
10192+
1018810193
/**
1018910194
* @dataProvider dataBug2574
1019010195
* @dataProvider dataBug2577
@@ -10265,6 +10270,7 @@ public function dataClassConstantOnExpression(): array
1026510270
* @dataProvider dataThrowExpression
1026610271
* @dataProvider dataNotEmptyArray
1026710272
* @dataProvider dataClassConstantOnExpression
10273+
* @dataProvider dataBug3961
1026810274
* @param string $assertType
1026910275
* @param string $file
1027010276
* @param mixed ...$args
+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Bug3961;
4+
5+
use function PHPStan\Analyser\assertType;
6+
7+
class Foo
8+
{
9+
10+
public function doFoo(string $v, string $d, $m): void
11+
{
12+
assertType('array<int, string>&nonEmpty', explode('.', $v));
13+
assertType('false', explode('', $v));
14+
assertType('array<int, string>', explode('.', $v, -2));
15+
assertType('array<int, string>&nonEmpty', explode('.', $v, 0));
16+
assertType('array<int, string>&nonEmpty', explode('.', $v, 1));
17+
assertType('array<int, string>|false', explode($d, $v));
18+
assertType('(array<int, string>|false)', explode($m, $v));
19+
}
20+
21+
}

0 commit comments

Comments
 (0)