Skip to content

Commit 5872b16

Browse files
committed
Merge branch 'get_value_return_extension'
2 parents 2ca8c85 + 0e7eca1 commit 5872b16

21 files changed

+769
-23
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
> It moves PHP closer to compiled languages in the sense that the correctness
1010
> of each line of the code can be checked before you run the actual line.
1111
12-
This PHPStan extension makes enumerator accessor methods known to PHPStan.
12+
This PHPStan extension makes enumerator accessor methods and enum possible values known to PHPStan.
1313

1414
## Install
1515

composer.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
"license": "BSD-3-Clause",
77
"require": {
88
"php": "~7.1",
9-
"marc-mabe/php-enum": "^1.0 || ^2.0 || ^3.0 || ^4.0",
9+
"marc-mabe/php-enum": "^1.1 || ^2.0 || ^3.0 || ^4.0",
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

+4
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@ services:
22
- class: MabeEnumPHPStan\EnumMethodsClassReflectionExtension
33
tags:
44
- phpstan.broker.methodsClassReflectionExtension
5+
6+
- class: MabeEnumPHPStan\EnumDynamicReturnTypeExtension
7+
tags:
8+
- phpstan.broker.dynamicMethodReturnTypeExtension
+132
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+
}

tests/Assets/AllTypeEnum.php

+17
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

+12
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

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MabeEnumPHPStanTest\Assets;
6+
7+
use MabeEnum\Enum;
8+
9+
class BigStrEnum extends Enum
10+
{
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';
41+
}

tests/Assets/BoolTypeEnum.php

+13
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

+19
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

+13
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

+13
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

+3
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

+12
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

+13
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+
}

tests/Assets/StrEnum.php renamed to tests/Assets/VisibilityEnum.php

+1-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
use MabeEnum\Enum;
88

9-
class StrEnum extends Enum
9+
class VisibilityEnum extends Enum
1010
{
1111
/**
1212
* String const without visibility declaration
@@ -29,6 +29,4 @@ class StrEnum extends Enum
2929
* Public string const
3030
*/
3131
public const PUBLIC_STR = 'public str';
32-
33-
public const NO_DOC_BLOCK = 'no doc block';
3432
}

0 commit comments

Comments
 (0)