Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/Type/Php/RangeFunctionReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
Expand Down Expand Up @@ -86,7 +87,6 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,

if (
$startType instanceof IntegerType
&& $endType instanceof IntegerType
&& $stepType instanceof IntegerType
) {
return new ArrayType(new IntegerType(), new IntegerType());
Expand All @@ -100,6 +100,12 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
return new ArrayType(new IntegerType(), new FloatType());
}

if ($startType instanceof StringType
&& $endType instanceof StringType
&& $stepType instanceof IntegerType) {
return new ArrayType(new IntegerType(), new StringType());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I'd like the suggested logic from phpstan/phpstan#2378 (comment). These if statements won't cover all cases.

}

return new ArrayType(new IntegerType(), new UnionType([new IntegerType(), new FloatType()]));
}

Expand Down
138 changes: 138 additions & 0 deletions tests/PHPStan/Type/Php/RangeFunctionReturnTypeExtensionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php

namespace PHPStan\Type\Php;

use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Type\ArrayType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\StringType;
use PHPUnit\Framework\MockObject\MockObject;

class RangeFunctionReturnTypeExtensionTest extends \PHPStan\Testing\TestCase
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, this isn't at all how dynamic return type extensions are tested. Please add new dataProvider to NodeScopeResolverTest::testFileAsserts and test it there. THank you.

{
/**
* @var RangeFunctionReturnTypeExtension
*/
private $extension;

public function testIsFunctionSupported(): void
{
/** @var MockObject|FunctionReflection $reflectionFunctionMock */
$reflectionFunctionMock = $this->createMock(FunctionReflection::class);
$reflectionFunctionMock->expects($this->once())->method('getName')->willReturn('range');
$this->extension->isFunctionSupported($reflectionFunctionMock);
}

/**
* @phpstan-param class-string<Type> $startType
* @phpstan-param class-string<Type> $endType
* @phpstan-param class-string<Type> $stepType
* @phpstan-param class-string<Type> $returnType
*
* @dataProvider provideStartEndStopAndReturnType
*/
public function testArrayTypesDependingOnStartEndAndStepType(
string $startType,
string $endType,
string $stepType,
string $returnType
): void {
$scope = $this->createMock(Scope::class);
$scope->method('getType')
->willReturnOnConsecutiveCalls(
$this->createMock($startType),
$this->createMock($endType),
$this->createMock($stepType)
);

$type = $this->extension->getTypeFromFunctionCall($this->createMock(FunctionReflection::class),
$this->createFuncCallMock(), $scope);

$this->assertInstanceOf(ArrayType::class, $type);
$this->assertInstanceOf(IntegerType::class, $type->getIterableKeyType());
$this->assertInstanceOf($returnType, $type->getIterableValueType());
}

private function createFuncCallMock(): FuncCall
{
return new class('anything', [
$this->createExprFromType(),
$this->createExprFromType(),
$this->createExprFromType()
]) extends FuncCall {
};
}

/**
* @param string $argType
*
* @phpstan-param class-string<Type> $className
* @return Arg
*
*/
private function createExprFromType(): Arg
{
$expr = $this->createMock(Arg::class);
$expr->value = $this->createMock(Expr::class);

return $expr;
}

public function provideStartEndStopAndReturnType(): array
{
return [
'int,int,int' => [
'startType' => IntegerType::class,
'endType' => IntegerType::class,
'stepType' => IntegerType::class,
'returnType' => IntegerType::class,
],
'float,int,int' => [
'startType' => FloatType::class,
'endType' => IntegerType::class,
'stepType' => IntegerType::class,
'returnType' => FloatType::class,
],
'int,float,int' => [
'startType' => IntegerType::class,
'endType' => FloatType::class,
'stepType' => IntegerType::class,
'returnType' => IntegerType::class,
],
'float,float,float' => [
'startType' => FloatType::class,
'endType' => FloatType::class,
'stepType' => FloatType::class,
'returnType' => FloatType::class,
],
'float,int,float' => [
'startType' => FloatType::class,
'endType' => IntegerType::class,
'stepType' => FloatType::class,
'returnType' => FloatType::class,
],
'int,float,float' => [
'startType' => IntegerType::class,
'endType' => FloatType::class,
'stepType' => FloatType::class,
'returnType' => FloatType::class,
],
'string,string,int' => [
'startType' => StringType::class,
'endType' => StringType::class,
'stepType' => IntegerType::class,
'returnType' => StringType::class,
]
];
}

protected function setUp(): void
{
$this->extension = new RangeFunctionReturnTypeExtension();
}
}