Skip to content

Commit

Permalink
Merge pull request #1 from marc-mabe/get_value_return_extension
Browse files Browse the repository at this point in the history
refactoring & fix getValues return type
  • Loading branch information
adaamz authored Apr 20, 2020
2 parents be25773 + 332a6f1 commit 7c091f3
Show file tree
Hide file tree
Showing 23 changed files with 680 additions and 149 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"phpstan/phpstan": "^0.12"
},
"require-dev": {
"phpunit/phpunit": "^7.5"
"phpunit/phpunit": "^7.5.20"
},
"autoload": {
"psr-4": {
Expand Down
2 changes: 1 addition & 1 deletion extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ services:
tags:
- phpstan.broker.methodsClassReflectionExtension

- class: MabeEnumPHPStan\EnumGetValueDynamicReturnTypeExtension
- class: MabeEnumPHPStan\EnumDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
132 changes: 132 additions & 0 deletions src/EnumDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php

declare(strict_types=1);

namespace MabeEnumPHPStan;

use MabeEnum\Enum;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\ConstantTypeHelper;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;

class EnumDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
/**
* Buffer of all types of enumeration values
* @phpstan-var array<class-string<Enum>, Type[]>
*/
private $enumValueTypesBuffer = [];

/**
* Buffer of all types of enumeration ordinals
* @phpstan-var array<class-string<Enum>, Type[]>
*/
private $enumOrdinalTypesBuffer = [];

public function getClass(): string
{
return Enum::class;
}

public function isMethodSupported(MethodReflection $methodReflection): bool
{
$supportedMethods = ['getvalue'];
if (method_exists(Enum::class, 'getValues')) {
array_push($supportedMethods, 'getvalues');
}

return in_array(strtolower($methodReflection->getName()), $supportedMethods, true);
}

public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
): Type {
$callType = $scope->getType($methodCall->var);
$callClasses = $callType->getReferencedClasses();
$methodName = strtolower($methodReflection->getName());
$returnTypes = [];
foreach ($callClasses as $callClass) {
if (!is_subclass_of($callClass, Enum::class, true)) {
$returnTypes[] = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())
->getReturnType();
} else {
switch ($methodName) {
case 'getvalue':
$returnTypes[] = $this->enumGetValueReturnType($callClass);
break;
case 'getvalues':
$returnTypes[] = $this->enumGetValuesReturnType($callClass);
break;
default:
throw new ShouldNotHappenException("Method {$methodName} is not supported");
}
}
}

return TypeCombinator::union(...$returnTypes);
}

/**
* Returns types of all values of an enumeration
* @phpstan-param class-string<Enum> $enumeration
* @return Type[]
*/
private function enumValueTypes(string $enumeration): array
{
if (isset($this->enumValueTypesBuffer[$enumeration])) {
return $this->enumValueTypesBuffer[$enumeration];
}

$values = array_values($enumeration::getConstants());
$types = array_map([ConstantTypeHelper::class, 'getTypeFromValue'], $values);

return $this->enumValueTypesBuffer[$enumeration] = $types;
}

/**
* Returns types of all ordinals of an enumeration
* @phpstan-param class-string<Enum> $enumeration
* @return Type[]
*/
private function enumOrdinalTypes(string $enumeration): array
{
if (isset($this->enumOrdinalTypesBuffer[$enumeration])) {
return $this->enumOrdinalTypesBuffer[$enumeration];
}

$ordinals = array_keys($enumeration::getOrdinals());
$types = array_map([ConstantTypeHelper::class, 'getTypeFromValue'], $ordinals);

return $this->enumOrdinalTypesBuffer[$enumeration] = $types;
}

/**
* Returns return type of Enum::getValue()
* @phpstan-param class-string<Enum> $enumeration
*/
private function enumGetValueReturnType(string $enumeration): Type
{
return TypeCombinator::union(...$this->enumValueTypes($enumeration));
}

/**
* Returns return type of Enum::getValues()
* @phpstan-param class-string<Enum> $enumeration
*/
private function enumGetValuesReturnType(string $enumeration): ArrayType
{
$keyTypes = $this->enumOrdinalTypes($enumeration);
$valueTypes = $this->enumValueTypes($enumeration);
return new ConstantArrayType($keyTypes, $valueTypes, count($keyTypes));
}
}
70 changes: 0 additions & 70 deletions src/EnumGetValueDynamicReturnTypeExtension.php

This file was deleted.

17 changes: 17 additions & 0 deletions tests/Assets/AllTypeEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace MabeEnumPHPStanTest\Assets;

use MabeEnum\Enum;

class AllTypeEnum extends Enum
{
const NIL = null;
const BOOL = true;
const INT = 1;
const FLOAT = 1.1;
const STR = 'str';
const ARR = [null, true, 1, 1.1, 'str', []];
}
12 changes: 12 additions & 0 deletions tests/Assets/ArrayTypeEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace MabeEnumPHPStanTest\Assets;

use MabeEnum\Enum;

class ArrayTypeEnum extends Enum
{
const ARRAY = [[]];
}
39 changes: 30 additions & 9 deletions tests/Assets/BigStrEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,34 @@

class BigStrEnum extends Enum
{
const C_1 = 'c1';
const C_2 = 'c2';
const C_3 = 'c3';
const C_4 = 'c4';
const C_5 = 'c5';
const C_6 = 'c6';
const C_7 = 'c7';
const C_8 = 'c8';
const C_9 = 'c9';
const C_01 = '01';
const C_02 = '02';
const C_03 = '03';
const C_04 = '04';
const C_05 = '05';
const C_06 = '06';
const C_07 = '07';
const C_08 = '08';
const C_09 = '09';
const C_10 = '10';
const C_11 = '11';
const C_12 = '12';
const C_13 = '13';
const C_14 = '14';
const C_15 = '15';
const C_16 = '16';
const C_17 = '17';
const C_18 = '18';
const C_19 = '19';
const C_20 = '20';
const C_21 = '21';
const C_22 = '22';
const C_23 = '23';
const C_24 = '24';
const C_25 = '25';
const C_26 = '26';
const C_27 = '27';
const C_28 = '28';
const C_29 = '29';
const C_30 = '30';
}
13 changes: 13 additions & 0 deletions tests/Assets/BoolTypeEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace MabeEnumPHPStanTest\Assets;

use MabeEnum\Enum;

class BoolTypeEnum extends Enum
{
const BOOL_TRUE = true;
const BOOL_FALSE = false;
}
19 changes: 19 additions & 0 deletions tests/Assets/DocCommentEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace MabeEnumPHPStanTest\Assets;

use MabeEnum\Enum;

class DocCommentEnum extends Enum
{
/**
* With doc block
*
* @var string
*/
const WITH_DOC_BLOCK = 'with doc block';

const WITHOUT_DOC_BLOCK = 'without doc block';
}
13 changes: 13 additions & 0 deletions tests/Assets/FloatTypeEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace MabeEnumPHPStanTest\Assets;

use MabeEnum\Enum;

class FloatTypeEnum extends Enum
{
const FLOAT11 = 1.1;
const FLOAT12 = 1.2;
}
13 changes: 13 additions & 0 deletions tests/Assets/IntTypeEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace MabeEnumPHPStanTest\Assets;

use MabeEnum\Enum;

class IntTypeEnum extends Enum
{
const INT0 = 0;
const INT1 = 1;
}
3 changes: 3 additions & 0 deletions tests/Assets/NotAnEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ class NotAnEnum
private const PRIVATE_STR = 'private str';
protected const PROTECTED_STR = 'protected str';
public const PUBLIC_STR = 'public str';

public function getValue(): string {return __FUNCTION__; }
public function getValues(): array { return []; }
}
12 changes: 12 additions & 0 deletions tests/Assets/NullTypeEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace MabeEnumPHPStanTest\Assets;

use MabeEnum\Enum;

class NullTypeEnum extends Enum
{
const NULL = null;
}
13 changes: 13 additions & 0 deletions tests/Assets/StrTypeEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace MabeEnumPHPStanTest\Assets;

use MabeEnum\Enum;

class StrTypeEnum extends Enum
{
const STR1 = 'str1';
const STR2 = 'str2';
}
Loading

0 comments on commit 7c091f3

Please sign in to comment.