Skip to content

refactoring & fix getValues return type #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
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
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