Skip to content

Commit 7c091f3

Browse files
authored
Merge pull request #1 from marc-mabe/get_value_return_extension
refactoring & fix getValues return type
2 parents be25773 + 332a6f1 commit 7c091f3

23 files changed

+680
-149
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"phpstan/phpstan": "^0.12"
1111
},
1212
"require-dev": {
13-
"phpunit/phpunit": "^7.5"
13+
"phpunit/phpunit": "^7.5.20"
1414
},
1515
"autoload": {
1616
"psr-4": {

extension.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ services:
33
tags:
44
- phpstan.broker.methodsClassReflectionExtension
55

6-
- class: MabeEnumPHPStan\EnumGetValueDynamicReturnTypeExtension
6+
- class: MabeEnumPHPStan\EnumDynamicReturnTypeExtension
77
tags:
88
- phpstan.broker.dynamicMethodReturnTypeExtension
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MabeEnumPHPStan;
6+
7+
use MabeEnum\Enum;
8+
use PhpParser\Node\Expr\MethodCall;
9+
use PHPStan\Analyser\Scope;
10+
use PHPStan\Reflection\MethodReflection;
11+
use PHPStan\Reflection\ParametersAcceptorSelector;
12+
use PHPStan\ShouldNotHappenException;
13+
use PHPStan\Type\ArrayType;
14+
use PHPStan\Type\Constant\ConstantArrayType;
15+
use PHPStan\Type\ConstantTypeHelper;
16+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
17+
use PHPStan\Type\Type;
18+
use PHPStan\Type\TypeCombinator;
19+
20+
class EnumDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
21+
{
22+
/**
23+
* Buffer of all types of enumeration values
24+
* @phpstan-var array<class-string<Enum>, Type[]>
25+
*/
26+
private $enumValueTypesBuffer = [];
27+
28+
/**
29+
* Buffer of all types of enumeration ordinals
30+
* @phpstan-var array<class-string<Enum>, Type[]>
31+
*/
32+
private $enumOrdinalTypesBuffer = [];
33+
34+
public function getClass(): string
35+
{
36+
return Enum::class;
37+
}
38+
39+
public function isMethodSupported(MethodReflection $methodReflection): bool
40+
{
41+
$supportedMethods = ['getvalue'];
42+
if (method_exists(Enum::class, 'getValues')) {
43+
array_push($supportedMethods, 'getvalues');
44+
}
45+
46+
return in_array(strtolower($methodReflection->getName()), $supportedMethods, true);
47+
}
48+
49+
public function getTypeFromMethodCall(
50+
MethodReflection $methodReflection,
51+
MethodCall $methodCall,
52+
Scope $scope
53+
): Type {
54+
$callType = $scope->getType($methodCall->var);
55+
$callClasses = $callType->getReferencedClasses();
56+
$methodName = strtolower($methodReflection->getName());
57+
$returnTypes = [];
58+
foreach ($callClasses as $callClass) {
59+
if (!is_subclass_of($callClass, Enum::class, true)) {
60+
$returnTypes[] = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())
61+
->getReturnType();
62+
} else {
63+
switch ($methodName) {
64+
case 'getvalue':
65+
$returnTypes[] = $this->enumGetValueReturnType($callClass);
66+
break;
67+
case 'getvalues':
68+
$returnTypes[] = $this->enumGetValuesReturnType($callClass);
69+
break;
70+
default:
71+
throw new ShouldNotHappenException("Method {$methodName} is not supported");
72+
}
73+
}
74+
}
75+
76+
return TypeCombinator::union(...$returnTypes);
77+
}
78+
79+
/**
80+
* Returns types of all values of an enumeration
81+
* @phpstan-param class-string<Enum> $enumeration
82+
* @return Type[]
83+
*/
84+
private function enumValueTypes(string $enumeration): array
85+
{
86+
if (isset($this->enumValueTypesBuffer[$enumeration])) {
87+
return $this->enumValueTypesBuffer[$enumeration];
88+
}
89+
90+
$values = array_values($enumeration::getConstants());
91+
$types = array_map([ConstantTypeHelper::class, 'getTypeFromValue'], $values);
92+
93+
return $this->enumValueTypesBuffer[$enumeration] = $types;
94+
}
95+
96+
/**
97+
* Returns types of all ordinals of an enumeration
98+
* @phpstan-param class-string<Enum> $enumeration
99+
* @return Type[]
100+
*/
101+
private function enumOrdinalTypes(string $enumeration): array
102+
{
103+
if (isset($this->enumOrdinalTypesBuffer[$enumeration])) {
104+
return $this->enumOrdinalTypesBuffer[$enumeration];
105+
}
106+
107+
$ordinals = array_keys($enumeration::getOrdinals());
108+
$types = array_map([ConstantTypeHelper::class, 'getTypeFromValue'], $ordinals);
109+
110+
return $this->enumOrdinalTypesBuffer[$enumeration] = $types;
111+
}
112+
113+
/**
114+
* Returns return type of Enum::getValue()
115+
* @phpstan-param class-string<Enum> $enumeration
116+
*/
117+
private function enumGetValueReturnType(string $enumeration): Type
118+
{
119+
return TypeCombinator::union(...$this->enumValueTypes($enumeration));
120+
}
121+
122+
/**
123+
* Returns return type of Enum::getValues()
124+
* @phpstan-param class-string<Enum> $enumeration
125+
*/
126+
private function enumGetValuesReturnType(string $enumeration): ArrayType
127+
{
128+
$keyTypes = $this->enumOrdinalTypes($enumeration);
129+
$valueTypes = $this->enumValueTypes($enumeration);
130+
return new ConstantArrayType($keyTypes, $valueTypes, count($keyTypes));
131+
}
132+
}

src/EnumGetValueDynamicReturnTypeExtension.php

Lines changed: 0 additions & 70 deletions
This file was deleted.

tests/Assets/AllTypeEnum.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MabeEnumPHPStanTest\Assets;
6+
7+
use MabeEnum\Enum;
8+
9+
class AllTypeEnum extends Enum
10+
{
11+
const NIL = null;
12+
const BOOL = true;
13+
const INT = 1;
14+
const FLOAT = 1.1;
15+
const STR = 'str';
16+
const ARR = [null, true, 1, 1.1, 'str', []];
17+
}

tests/Assets/ArrayTypeEnum.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MabeEnumPHPStanTest\Assets;
6+
7+
use MabeEnum\Enum;
8+
9+
class ArrayTypeEnum extends Enum
10+
{
11+
const ARRAY = [[]];
12+
}

tests/Assets/BigStrEnum.php

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,34 @@
88

99
class BigStrEnum extends Enum
1010
{
11-
const C_1 = 'c1';
12-
const C_2 = 'c2';
13-
const C_3 = 'c3';
14-
const C_4 = 'c4';
15-
const C_5 = 'c5';
16-
const C_6 = 'c6';
17-
const C_7 = 'c7';
18-
const C_8 = 'c8';
19-
const C_9 = 'c9';
11+
const C_01 = '01';
12+
const C_02 = '02';
13+
const C_03 = '03';
14+
const C_04 = '04';
15+
const C_05 = '05';
16+
const C_06 = '06';
17+
const C_07 = '07';
18+
const C_08 = '08';
19+
const C_09 = '09';
20+
const C_10 = '10';
21+
const C_11 = '11';
22+
const C_12 = '12';
23+
const C_13 = '13';
24+
const C_14 = '14';
25+
const C_15 = '15';
26+
const C_16 = '16';
27+
const C_17 = '17';
28+
const C_18 = '18';
29+
const C_19 = '19';
30+
const C_20 = '20';
31+
const C_21 = '21';
32+
const C_22 = '22';
33+
const C_23 = '23';
34+
const C_24 = '24';
35+
const C_25 = '25';
36+
const C_26 = '26';
37+
const C_27 = '27';
38+
const C_28 = '28';
39+
const C_29 = '29';
40+
const C_30 = '30';
2041
}

tests/Assets/BoolTypeEnum.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MabeEnumPHPStanTest\Assets;
6+
7+
use MabeEnum\Enum;
8+
9+
class BoolTypeEnum extends Enum
10+
{
11+
const BOOL_TRUE = true;
12+
const BOOL_FALSE = false;
13+
}

tests/Assets/DocCommentEnum.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MabeEnumPHPStanTest\Assets;
6+
7+
use MabeEnum\Enum;
8+
9+
class DocCommentEnum extends Enum
10+
{
11+
/**
12+
* With doc block
13+
*
14+
* @var string
15+
*/
16+
const WITH_DOC_BLOCK = 'with doc block';
17+
18+
const WITHOUT_DOC_BLOCK = 'without doc block';
19+
}

tests/Assets/FloatTypeEnum.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MabeEnumPHPStanTest\Assets;
6+
7+
use MabeEnum\Enum;
8+
9+
class FloatTypeEnum extends Enum
10+
{
11+
const FLOAT11 = 1.1;
12+
const FLOAT12 = 1.2;
13+
}

tests/Assets/IntTypeEnum.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MabeEnumPHPStanTest\Assets;
6+
7+
use MabeEnum\Enum;
8+
9+
class IntTypeEnum extends Enum
10+
{
11+
const INT0 = 0;
12+
const INT1 = 1;
13+
}

tests/Assets/NotAnEnum.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,7 @@ class NotAnEnum
1010
private const PRIVATE_STR = 'private str';
1111
protected const PROTECTED_STR = 'protected str';
1212
public const PUBLIC_STR = 'public str';
13+
14+
public function getValue(): string {return __FUNCTION__; }
15+
public function getValues(): array { return []; }
1316
}

tests/Assets/NullTypeEnum.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MabeEnumPHPStanTest\Assets;
6+
7+
use MabeEnum\Enum;
8+
9+
class NullTypeEnum extends Enum
10+
{
11+
const NULL = null;
12+
}

tests/Assets/StrTypeEnum.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MabeEnumPHPStanTest\Assets;
6+
7+
use MabeEnum\Enum;
8+
9+
class StrTypeEnum extends Enum
10+
{
11+
const STR1 = 'str1';
12+
const STR2 = 'str2';
13+
}

0 commit comments

Comments
 (0)