Skip to content

Commit

Permalink
Merge branch 'get_value_return_extension'
Browse files Browse the repository at this point in the history
  • Loading branch information
marc-mabe committed Apr 21, 2020
2 parents 2ca8c85 + 0e7eca1 commit 5872b16
Show file tree
Hide file tree
Showing 21 changed files with 769 additions and 23 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
> It moves PHP closer to compiled languages in the sense that the correctness
> of each line of the code can be checked before you run the actual line.
This PHPStan extension makes enumerator accessor methods known to PHPStan.
This PHPStan extension makes enumerator accessor methods and enum possible values known to PHPStan.

## Install

Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
"license": "BSD-3-Clause",
"require": {
"php": "~7.1",
"marc-mabe/php-enum": "^1.0 || ^2.0 || ^3.0 || ^4.0",
"marc-mabe/php-enum": "^1.1 || ^2.0 || ^3.0 || ^4.0",
"phpstan/phpstan": "^0.12"
},
"require-dev": {
"phpunit/phpunit": "^7.5"
"phpunit/phpunit": "^7.5.20"
},
"autoload": {
"psr-4": {
Expand Down
4 changes: 4 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ services:
- class: MabeEnumPHPStan\EnumMethodsClassReflectionExtension
tags:
- phpstan.broker.methodsClassReflectionExtension

- 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));
}
}
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 = [[]];
}
41 changes: 41 additions & 0 deletions tests/Assets/BigStrEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace MabeEnumPHPStanTest\Assets;

use MabeEnum\Enum;

class BigStrEnum extends Enum
{
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';
}
4 changes: 1 addition & 3 deletions tests/Assets/StrEnum.php → tests/Assets/VisibilityEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use MabeEnum\Enum;

class StrEnum extends Enum
class VisibilityEnum extends Enum
{
/**
* String const without visibility declaration
Expand All @@ -29,6 +29,4 @@ class StrEnum extends Enum
* Public string const
*/
public const PUBLIC_STR = 'public str';

public const NO_DOC_BLOCK = 'no doc block';
}
Loading

0 comments on commit 5872b16

Please sign in to comment.