Skip to content

Commit

Permalink
explode() returns a non-empty array in certain scenarios
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Oct 17, 2020
1 parent 7219419 commit 0069107
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 1 deletion.
13 changes: 12 additions & 1 deletion src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\Accessory\NonEmptyArrayType;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;

class ExplodeFunctionDynamicReturnTypeExtension implements \PHPStan\Type\DynamicFunctionReturnTypeExtension
Expand All @@ -38,7 +41,15 @@ public function getTypeFromFunctionCall(
if ($isSuperset->yes()) {
return new ConstantBooleanType(false);
} elseif ($isSuperset->no()) {
return new ArrayType(new IntegerType(), new StringType());
$arrayType = new ArrayType(new IntegerType(), new StringType());
if (
!isset($functionCall->args[2])
|| IntegerRangeType::fromInterval(0, null)->isSuperTypeOf($scope->getType($functionCall->args[2]->value))->yes()
) {
return TypeCombinator::intersect($arrayType, new NonEmptyArrayType());
}

return $arrayType;
}

$returnType = ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
Expand Down
6 changes: 6 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10185,6 +10185,11 @@ public function dataClassConstantOnExpression(): array
return $this->gatherAssertTypes(__DIR__ . '/data/class-constant-on-expr.php');
}

public function dataBug3961(): array
{
return $this->gatherAssertTypes(__DIR__ . '/data/bug-3961.php');
}

/**
* @dataProvider dataBug2574
* @dataProvider dataBug2577
Expand Down Expand Up @@ -10265,6 +10270,7 @@ public function dataClassConstantOnExpression(): array
* @dataProvider dataThrowExpression
* @dataProvider dataNotEmptyArray
* @dataProvider dataClassConstantOnExpression
* @dataProvider dataBug3961
* @param string $assertType
* @param string $file
* @param mixed ...$args
Expand Down
21 changes: 21 additions & 0 deletions tests/PHPStan/Analyser/data/bug-3961.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Bug3961;

use function PHPStan\Analyser\assertType;

class Foo
{

public function doFoo(string $v, string $d, $m): void
{
assertType('array<int, string>&nonEmpty', explode('.', $v));
assertType('false', explode('', $v));
assertType('array<int, string>', explode('.', $v, -2));
assertType('array<int, string>&nonEmpty', explode('.', $v, 0));
assertType('array<int, string>&nonEmpty', explode('.', $v, 1));
assertType('array<int, string>|false', explode($d, $v));

This comment has been minimized.

Copy link
@hrach

hrach Oct 17, 2020

Contributor

Why this is not (array<int, string>&nonEmpty)|false ?

This comment has been minimized.

Copy link
@ondrejmirtes

ondrejmirtes Oct 17, 2020

Author Member

I don't know, test whether it's possible and send a PR, thanks. We should also test explode($d, $v, -1), where the empty array is probably possible.

assertType('(array<int, string>|false)', explode($m, $v));
}

}

0 comments on commit 0069107

Please sign in to comment.