1111use PHPStan \Reflection \ParametersAcceptorSelector ;
1212use PHPStan \ShouldNotHappenException ;
1313use PHPStan \Type \ArrayType ;
14+ use PHPStan \Type \Constant \ConstantArrayType ;
15+ use PHPStan \Type \ConstantTypeHelper ;
1416use PHPStan \Type \DynamicMethodReturnTypeExtension ;
15- use PHPStan \Type \IntegerType ;
1617use PHPStan \Type \Type ;
1718use PHPStan \Type \TypeCombinator ;
1819
1920class EnumDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
2021{
2122 /**
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[]>
2525 */
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 = [];
2733
2834 public function getClass (): string
2935 {
@@ -40,45 +46,87 @@ public function isMethodSupported(MethodReflection $methodReflection): bool
4046 return in_array (strtolower ($ methodReflection ->getName ()), $ supportedMethods , true );
4147 }
4248
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+ }
5074 }
5175
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 );
6577 }
6678
6779 /**
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[]
7083 */
71- private function getEnumValuesType (string $ enumeration, Scope $ scope ): Type
84+ private function enumValueTypes (string $ enumeration ): array
7285 {
73- if (isset ($ this ->enumValuesTypeBuffer [$ enumeration ])) {
74- return $ this ->enumValuesTypeBuffer [$ enumeration ];
86+ if (isset ($ this ->enumValueTypesBuffer [$ enumeration ])) {
87+ return $ this ->enumValueTypesBuffer [$ enumeration ];
7588 }
7689
7790 $ 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+ }
81106
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 ));
83131 }
84132}
0 commit comments