11
11
use PHPStan \Reflection \ParametersAcceptorSelector ;
12
12
use PHPStan \ShouldNotHappenException ;
13
13
use PHPStan \Type \ArrayType ;
14
+ use PHPStan \Type \Constant \ConstantArrayType ;
15
+ use PHPStan \Type \ConstantTypeHelper ;
14
16
use PHPStan \Type \DynamicMethodReturnTypeExtension ;
15
- use PHPStan \Type \IntegerType ;
16
17
use PHPStan \Type \Type ;
17
18
use PHPStan \Type \TypeCombinator ;
18
19
19
20
class EnumDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
20
21
{
21
22
/**
22
- * Buffer of known return types of Enum::getValues()
23
- * @var Type[]
24
- * @phpstan-var array<class-string<Enum>, Type>
23
+ * Buffer of all types of enumeration values
24
+ * @phpstan-var array<class-string<Enum>, Type[]>
25
25
*/
26
- private $ enumValuesTypeBuffer = [];
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 = [];
27
33
28
34
public function getClass (): string
29
35
{
@@ -40,45 +46,87 @@ public function isMethodSupported(MethodReflection $methodReflection): bool
40
46
return in_array (strtolower ($ methodReflection ->getName ()), $ supportedMethods , true );
41
47
}
42
48
43
- public function getTypeFromMethodCall (MethodReflection $ methodReflection , MethodCall $ methodCall , Scope $ scope ): Type
44
- {
45
- $ enumType = $ scope ->getType ($ methodCall ->var );
46
- $ methodName = $ methodReflection ->getName ();
47
- $ methodClasses = $ enumType ->getReferencedClasses ();
48
- if (count ($ methodClasses ) !== 1 ) {
49
- return ParametersAcceptorSelector::selectSingle ($ methodReflection ->getVariants ())->getReturnType ();
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
+ }
50
74
}
51
75
52
- $ enumeration = $ methodClasses [0 ];
53
-
54
- switch (strtolower ($ methodName )) {
55
- case 'getvalue ' :
56
- return $ this ->getEnumValuesType ($ enumeration , $ scope );
57
- case 'getvalues ' :
58
- return new ArrayType (
59
- new IntegerType (),
60
- $ this ->getEnumValuesType ($ enumeration , $ scope )
61
- );
62
- default :
63
- throw new ShouldNotHappenException ("Method {$ methodName } is not supported " );
64
- }
76
+ return TypeCombinator::union (...$ returnTypes );
65
77
}
66
78
67
79
/**
68
- * Returns union type of all values of an enumeration
69
- * @phpstan-param class-string<Enum> $enumClass
80
+ * Returns types of all values of an enumeration
81
+ * @phpstan-param class-string<Enum> $enumeration
82
+ * @return Type[]
70
83
*/
71
- private function getEnumValuesType (string $ enumeration, Scope $ scope ): Type
84
+ private function enumValueTypes (string $ enumeration ): array
72
85
{
73
- if (isset ($ this ->enumValuesTypeBuffer [$ enumeration ])) {
74
- return $ this ->enumValuesTypeBuffer [$ enumeration ];
86
+ if (isset ($ this ->enumValueTypesBuffer [$ enumeration ])) {
87
+ return $ this ->enumValueTypesBuffer [$ enumeration ];
75
88
}
76
89
77
90
$ values = array_values ($ enumeration ::getConstants ());
78
- $ types = array_map (function ($ value ) use ($ scope ): Type {
79
- return $ scope ->getTypeFromValue ($ value );
80
- }, $ values );
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
+ }
81
106
82
- return $ this ->enumValuesTypeBuffer [$ enumeration ] = TypeCombinator::union (...$ types );
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 ));
83
131
}
84
132
}
0 commit comments