1414use PHPStan \Type \Constant \ConstantBooleanType ;
1515use PHPStan \Type \Constant \ConstantStringType ;
1616use PHPStan \Type \DynamicMethodReturnTypeExtension ;
17- use PHPStan \Type \ObjectWithoutClassType ;
1817use PHPStan \Type \Type ;
1918use PHPStan \Type \TypeCombinator ;
2019use ReflectionClass ;
2120use function count ;
22- use function is_int ;
2321
2422#[AutowiredService]
2523final class ReflectionClassGetConstantsDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
@@ -41,16 +39,12 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
4139 $ calledOnType = $ scope ->getType ($ methodCall ->var );
4240 $ reflectionType = $ calledOnType ->getTemplateType (ReflectionClass::class, 'T ' );
4341
44- if ((new ObjectWithoutClassType ())->isSuperTypeOf ($ reflectionType )->no ()) {
45- return null ;
46- }
47-
4842 $ classReflections = $ reflectionType ->getObjectClassReflections ();
4943 if (count ($ classReflections ) === 0 ) {
5044 return null ;
5145 }
5246
53- if ($ this ->isCovariantWithNonFinalClass ($ calledOnType , $ classReflections )) {
47+ if (! $ this ->isInvariantOrFinalClass ($ calledOnType , $ classReflections )) {
5448 return null ;
5549 }
5650
@@ -65,17 +59,38 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
6559 return $ this ->resolveGetConstants ($ scope , $ classReflections , $ filterType );
6660 }
6761
68- /** @param non-empty-string $name */
69- private function getConstantType (Scope $ scope , ClassReflection $ classReflection , string $ name ): Type
62+ /**
63+ * @param non-empty-list<ClassReflection> $classReflections
64+ */
65+ private function isInvariantOrFinalClass (Type $ calledOnType , array $ classReflections ): bool
7066 {
71- return $ scope ->getType (new ClassConstFetch (
72- new FullyQualified ($ classReflection ->getName ()),
73- new Identifier ($ name ),
74- ));
67+ $ hasNonFinalClass = false ;
68+ foreach ($ classReflections as $ classReflection ) {
69+ if (!$ classReflection ->isFinal () && !$ classReflection ->isEnum ()) {
70+ $ hasNonFinalClass = true ;
71+ break ;
72+ }
73+ }
74+
75+ if (!$ hasNonFinalClass ) {
76+ return true ;
77+ }
78+
79+ foreach ($ calledOnType ->getObjectClassReflections () as $ reflectionClassReflection ) {
80+ if ($ reflectionClassReflection ->getName () !== 'ReflectionClass ' ) {
81+ return false ;
82+ }
83+ $ variance = $ reflectionClassReflection ->getCallSiteVarianceMap ()->getVariance ('T ' );
84+ if ($ variance !== null && $ variance ->covariant ()) {
85+ return false ;
86+ }
87+ }
88+
89+ return true ;
7590 }
7691
7792 /**
78- * @param list<ClassReflection> $classReflections
93+ * @param non-empty- list<ClassReflection> $classReflections
7994 */
8095 private function resolveGetConstant (MethodCall $ methodCall , Scope $ scope , array $ classReflections ): ?Type
8196 {
@@ -126,70 +141,14 @@ private function resolveGetConstant(MethodCall $methodCall, Scope $scope, array
126141 }
127142
128143 /**
129- * @param list<ClassReflection> $classReflections
144+ * @param non-empty-string $name
130145 */
131- private function resolveGetConstants (Scope $ scope , array $ classReflections , ?Type $ filterType ): ?Type
132- {
133- if ($ filterType === null ) {
134- return $ this ->buildConstantsArray ($ scope , $ classReflections , null , false );
135- }
136-
137- $ filterScalars = $ filterType ->getConstantScalarValues ();
138- $ intFilters = [];
139- foreach ($ filterScalars as $ scalar ) {
140- if (!is_int ($ scalar )) {
141- $ intFilters = null ;
142- break ;
143- }
144- $ intFilters [] = $ scalar ;
145- }
146-
147- if ($ intFilters !== null && count ($ intFilters ) === 1 ) {
148- return $ this ->buildConstantsArray ($ scope , $ classReflections , $ intFilters [0 ], false );
149- }
150-
151- if ($ intFilters !== null && count ($ intFilters ) > 1 ) {
152- $ types = [];
153- foreach ($ intFilters as $ filter ) {
154- $ result = $ this ->buildConstantsArray ($ scope , $ classReflections , $ filter , false );
155- if ($ result !== null ) {
156- $ types [] = $ result ;
157- }
158- }
159-
160- if (count ($ types ) === 0 ) {
161- return null ;
162- }
163-
164- return TypeCombinator::union (...$ types );
165- }
166-
167- return $ this ->buildConstantsArray ($ scope , $ classReflections , null , true );
168- }
169-
170- /**
171- * @param list<ClassReflection> $classReflections
172- */
173- private function buildConstantsArray (Scope $ scope , array $ classReflections , ?int $ filter , bool $ optional ): ?Type
146+ private function getConstantType (Scope $ scope , ClassReflection $ classReflection , string $ name ): Type
174147 {
175- $ types = [];
176- foreach ($ classReflections as $ classReflection ) {
177- $ builder = ConstantArrayTypeBuilder::createEmpty ();
178- foreach ($ this ->getConstantNames ($ classReflection , $ filter ) as $ name ) {
179- $ builder ->setOffsetValueType (
180- new ConstantStringType ($ name ),
181- $ this ->getConstantType ($ scope , $ classReflection , $ name ),
182- $ optional ,
183- );
184- }
185- $ types [] = $ builder ->getArray ();
186- }
187-
188- if (count ($ types ) === 0 ) {
189- return null ;
190- }
191-
192- return TypeCombinator::union (...$ types );
148+ return $ scope ->getType (new ClassConstFetch (
149+ new FullyQualified ($ classReflection ->getName ()),
150+ new Identifier ($ name ),
151+ ));
193152 }
194153
195154 /**
@@ -214,32 +173,47 @@ private function getConstantNames(ClassReflection $classReflection, ?int $filter
214173 return $ names ;
215174 }
216175
217- /** @param list<ClassReflection> $classReflections */
218- private function isCovariantWithNonFinalClass (Type $ calledOnType , array $ classReflections ): bool
176+ /**
177+ * @param non-empty-list<ClassReflection> $classReflections
178+ */
179+ private function resolveGetConstants (Scope $ scope , array $ classReflections , ?Type $ filterType ): ?Type
219180 {
220- $ hasNonFinalClass = false ;
221- foreach ($ classReflections as $ classReflection ) {
222- if (!$ classReflection ->isFinal () && !$ classReflection ->isEnum ()) {
223- $ hasNonFinalClass = true ;
224- break ;
225- }
181+ if ($ filterType === null ) {
182+ return $ this ->buildConstantsArray ($ scope , $ classReflections , null , false );
226183 }
227184
228- if (!$ hasNonFinalClass ) {
229- return false ;
185+ $ filterScalars = $ filterType ->getConstantScalarValues ();
186+ if (count ($ filterScalars ) === 0 ) {
187+ return $ this ->buildConstantsArray ($ scope , $ classReflections , null , true );
230188 }
231189
232- foreach ($ calledOnType ->getObjectClassReflections () as $ reflectionClassReflection ) {
233- if ($ reflectionClassReflection ->getName () !== 'ReflectionClass ' ) {
234- continue ;
235- }
236- $ variance = $ reflectionClassReflection ->getCallSiteVarianceMap ()->getVariance ('T ' );
237- if ($ variance !== null && $ variance ->covariant ()) {
238- return true ;
190+ $ types = [];
191+ foreach ($ filterScalars as $ filter ) {
192+ $ types [] = $ this ->buildConstantsArray ($ scope , $ classReflections , (int ) $ filter , false );
193+ }
194+
195+ return TypeCombinator::union (...$ types );
196+ }
197+
198+ /**
199+ * @param non-empty-list<ClassReflection> $classReflections
200+ */
201+ private function buildConstantsArray (Scope $ scope , array $ classReflections , ?int $ filter , bool $ optional ): Type
202+ {
203+ $ types = [];
204+ foreach ($ classReflections as $ classReflection ) {
205+ $ builder = ConstantArrayTypeBuilder::createEmpty ();
206+ foreach ($ this ->getConstantNames ($ classReflection , $ filter ) as $ name ) {
207+ $ builder ->setOffsetValueType (
208+ new ConstantStringType ($ name ),
209+ $ this ->getConstantType ($ scope , $ classReflection , $ name ),
210+ $ optional ,
211+ );
239212 }
213+ $ types [] = $ builder ->getArray ();
240214 }
241215
242- return false ;
216+ return TypeCombinator:: union (... $ types ) ;
243217 }
244218
245219}
0 commit comments