From 00691075acb1db2d1a6fbdbf607e0b52271815bb Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sat, 17 Oct 2020 04:36:39 +0200 Subject: [PATCH] explode() returns a non-empty array in certain scenarios --- ...lodeFunctionDynamicReturnTypeExtension.php | 13 +++++++++++- .../Analyser/NodeScopeResolverTest.php | 6 ++++++ tests/PHPStan/Analyser/data/bug-3961.php | 21 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/data/bug-3961.php diff --git a/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php b/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php index fdead7b788..891f942668 100644 --- a/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php +++ b/src/Type/Php/ExplodeFunctionDynamicReturnTypeExtension.php @@ -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 @@ -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(); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 2ba9d034db..a465b0d006 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -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 @@ -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 diff --git a/tests/PHPStan/Analyser/data/bug-3961.php b/tests/PHPStan/Analyser/data/bug-3961.php new file mode 100644 index 0000000000..c3eced6bda --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-3961.php @@ -0,0 +1,21 @@ +&nonEmpty', explode('.', $v)); + assertType('false', explode('', $v)); + assertType('array', explode('.', $v, -2)); + assertType('array&nonEmpty', explode('.', $v, 0)); + assertType('array&nonEmpty', explode('.', $v, 1)); + assertType('array|false', explode($d, $v)); + assertType('(array|false)', explode($m, $v)); + } + +}