Skip to content

Commit af744c1

Browse files
committed
refactoring & fix getValues return type
1 parent 617dfd0 commit af744c1

19 files changed

+408
-146
lines changed

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
"require": {
88
"php": "~7.1",
99
"marc-mabe/php-enum": "^1.0 || ^2.0 || ^3.0 || ^4.0",
10-
"phpstan/phpstan": "^0.12"
10+
"phpstan/phpstan": "^0.12.18"
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: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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\BooleanType;
15+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
16+
use PHPStan\Type\FloatType;
17+
use PHPStan\Type\IntegerType;
18+
use PHPStan\Type\StringType;
19+
use PHPStan\Type\Type;
20+
use PHPStan\Type\TypeCombinator;
21+
use PHPStan\Type\VerbosityLevel;
22+
23+
class EnumDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
24+
{
25+
const SUPPORTED_METHODS = ['getvalue', 'getvalues'];
26+
27+
/**
28+
* Buffer of known return types of Enum::getValues()
29+
* @var Type[]
30+
* @phpstan-var array<class-string<Enum>, Type>
31+
*/
32+
private $enumValuesTypeBuffer = [];
33+
34+
public function getClass(): string
35+
{
36+
return Enum::class;
37+
}
38+
39+
public function isMethodSupported(MethodReflection $methodReflection): bool
40+
{
41+
return in_array(strtolower($methodReflection->getName()), self::SUPPORTED_METHODS, true);
42+
}
43+
44+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
45+
{
46+
$enumType = $scope->getType($methodCall->var);
47+
$methodName = $methodReflection->getName();
48+
$methodClasses = $enumType->getReferencedClasses();
49+
if (count($methodClasses) !== 1) {
50+
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
51+
}
52+
53+
$enumeration = $methodClasses[0];
54+
55+
switch (strtolower($methodName)) {
56+
case 'getvalue':
57+
return $this->getEnumValuesType($enumeration, $scope);
58+
case 'getvalues':
59+
return new ArrayType(
60+
new IntegerType(),
61+
$this->getEnumValuesType($enumeration, $scope)
62+
);
63+
default:
64+
throw new ShouldNotHappenException("Method {$methodName} is not supported");
65+
}
66+
}
67+
68+
/**
69+
* Returns union type of all values of an enumeration
70+
* @phpstan-param class-string<Enum> $enumClass
71+
*/
72+
private function getEnumValuesType(string $enumeration, Scope $scope): Type
73+
{
74+
if (isset($this->enumValuesTypeBuffer[$enumeration])) {
75+
return $this->enumValuesTypeBuffer[$enumeration];
76+
}
77+
78+
$values = array_values($enumeration::getConstants());
79+
$types = array_map(function ($value) use ($scope): Type {
80+
return $scope->getTypeFromValue($value);
81+
}, $values);
82+
83+
return $this->enumValuesTypeBuffer[$enumeration] = TypeCombinator::union(...$types);
84+
}
85+
}

src/EnumGetValueDynamicReturnTypeExtension.php

Lines changed: 0 additions & 73 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/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/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+
}

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

Lines changed: 1 addition & 3 deletions
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)