35
35
use PHPStan \Analyser \TypeSpecifierAwareExtension ;
36
36
use PHPStan \Analyser \TypeSpecifierContext ;
37
37
use PHPStan \Reflection \MethodReflection ;
38
+ use PHPStan \Reflection \ReflectionProvider ;
38
39
use PHPStan \ShouldNotHappenException ;
39
40
use PHPStan \Type \ArrayType ;
40
- use PHPStan \Type \Constant \ConstantArrayType ;
41
41
use PHPStan \Type \Constant \ConstantArrayTypeBuilder ;
42
42
use PHPStan \Type \Constant \ConstantBooleanType ;
43
- use PHPStan \Type \Constant \ConstantStringType ;
44
43
use PHPStan \Type \IterableType ;
45
44
use PHPStan \Type \MixedType ;
46
45
use PHPStan \Type \NeverType ;
47
- use PHPStan \Type \ObjectType ;
48
46
use PHPStan \Type \StaticMethodTypeSpecifyingExtension ;
49
47
use PHPStan \Type \StringType ;
50
48
use PHPStan \Type \Type ;
51
49
use PHPStan \Type \TypeCombinator ;
52
- use PHPStan \Type \TypeWithClassName ;
53
50
use ReflectionObject ;
54
51
use Traversable ;
55
52
use function array_filter ;
@@ -66,11 +63,19 @@ class AssertTypeSpecifyingExtension implements StaticMethodTypeSpecifyingExtensi
66
63
{
67
64
68
65
/** @var Closure[] */
69
- private static $ resolvers ;
66
+ private $ resolvers ;
67
+
68
+ /** @var ReflectionProvider */
69
+ private $ reflectionProvider ;
70
70
71
71
/** @var TypeSpecifier */
72
72
private $ typeSpecifier ;
73
73
74
+ public function __construct (ReflectionProvider $ reflectionProvider )
75
+ {
76
+ $ this ->reflectionProvider = $ reflectionProvider ;
77
+ }
78
+
74
79
public function setTypeSpecifier (TypeSpecifier $ typeSpecifier ): void
75
80
{
76
81
$ this ->typeSpecifier = $ typeSpecifier ;
@@ -98,7 +103,7 @@ public function isStaticMethodSupported(
98
103
}
99
104
100
105
$ trimmedName = self ::trimName ($ staticMethodReflection ->getName ());
101
- $ resolvers = self :: getExpressionResolvers ();
106
+ $ resolvers = $ this -> getExpressionResolvers ();
102
107
103
108
if (!array_key_exists ($ trimmedName , $ resolvers )) {
104
109
return false ;
@@ -176,14 +181,14 @@ static function (Type $type) {
176
181
* @param Arg[] $args
177
182
* @return array{?Expr, ?Expr}
178
183
*/
179
- private static function createExpression (
184
+ private function createExpression (
180
185
Scope $ scope ,
181
186
string $ name ,
182
187
array $ args
183
188
): array
184
189
{
185
190
$ trimmedName = self ::trimName ($ name );
186
- $ resolvers = self :: getExpressionResolvers ();
191
+ $ resolvers = $ this -> getExpressionResolvers ();
187
192
$ resolver = $ resolvers [$ trimmedName ];
188
193
189
194
$ resolverResult = $ resolver ($ scope , ...$ args );
@@ -214,10 +219,10 @@ private static function createExpression(
214
219
/**
215
220
* @return array<string, callable(Scope, Arg...): (Expr|array{?Expr, ?Expr}|null)>
216
221
*/
217
- private static function getExpressionResolvers (): array
222
+ private function getExpressionResolvers (): array
218
223
{
219
- if (self :: $ resolvers === null ) {
220
- self :: $ resolvers = [
224
+ if ($ this -> resolvers === null ) {
225
+ $ this -> resolvers = [
221
226
'integer ' => static function (Scope $ scope , Arg $ value ): Expr {
222
227
return new FuncCall (
223
228
new Name ('is_int ' ),
@@ -328,8 +333,8 @@ private static function getExpressionResolvers(): array
328
333
[$ value ]
329
334
);
330
335
},
331
- 'isTraversable ' => static function (Scope $ scope , Arg $ value ): Expr {
332
- return self :: $ resolvers ['isIterable ' ]($ scope , $ value );
336
+ 'isTraversable ' => function (Scope $ scope , Arg $ value ): Expr {
337
+ return $ this -> resolvers ['isIterable ' ]($ scope , $ value );
333
338
},
334
339
'isIterable ' => static function (Scope $ scope , Arg $ expr ): Expr {
335
340
return new BooleanOr (
@@ -358,9 +363,9 @@ private static function getExpressionResolvers(): array
358
363
)
359
364
);
360
365
},
361
- 'isNonEmptyList ' => static function (Scope $ scope , Arg $ expr ): Expr {
366
+ 'isNonEmptyList ' => function (Scope $ scope , Arg $ expr ): Expr {
362
367
return new BooleanAnd (
363
- self :: $ resolvers ['isList ' ]($ scope , $ expr ),
368
+ $ this -> resolvers ['isList ' ]($ scope , $ expr ),
364
369
new NotIdentical (
365
370
$ expr ->value ,
366
371
new Array_ ()
@@ -382,9 +387,9 @@ private static function getExpressionResolvers(): array
382
387
)
383
388
);
384
389
},
385
- 'isNonEmptyMap ' => static function (Scope $ scope , Arg $ expr ): Expr {
390
+ 'isNonEmptyMap ' => function (Scope $ scope , Arg $ expr ): Expr {
386
391
return new BooleanAnd (
387
- self :: $ resolvers ['isMap ' ]($ scope , $ expr ),
392
+ $ this -> resolvers ['isMap ' ]($ scope , $ expr ),
388
393
new NotIdentical (
389
394
$ expr ->value ,
390
395
new Array_ ()
@@ -405,24 +410,22 @@ private static function getExpressionResolvers(): array
405
410
},
406
411
'isInstanceOf ' => static function (Scope $ scope , Arg $ expr , Arg $ class ): ?Expr {
407
412
$ classType = $ scope ->getType ($ class ->value );
408
- if ($ classType instanceof ConstantStringType) {
409
- $ className = new Name ($ classType ->getValue ());
410
- } elseif ($ classType instanceof TypeWithClassName) {
411
- $ className = new Name ($ classType ->getClassName ());
412
- } else {
413
+ $ classNameType = $ classType ->getObjectTypeOrClassStringObjectType ();
414
+ $ classNames = $ classNameType ->getObjectClassNames ();
415
+ if (count ($ classNames ) !== 1 ) {
413
416
return null ;
414
417
}
415
418
416
419
return new Instanceof_ (
417
420
$ expr ->value ,
418
- $ className
421
+ new Name ( $ classNames [ 0 ])
419
422
);
420
423
},
421
- 'isInstanceOfAny ' => static function (Scope $ scope , Arg $ expr , Arg $ classes ): ?Expr {
422
- return self ::buildAnyOfExpr ($ scope , $ expr , $ classes , self :: $ resolvers ['isInstanceOf ' ]);
424
+ 'isInstanceOfAny ' => function (Scope $ scope , Arg $ expr , Arg $ classes ): ?Expr {
425
+ return self ::buildAnyOfExpr ($ scope , $ expr , $ classes , $ this -> resolvers ['isInstanceOf ' ]);
423
426
},
424
- 'notInstanceOf ' => static function (Scope $ scope , Arg $ expr , Arg $ class ): ?Expr {
425
- $ expr = self :: $ resolvers ['isInstanceOf ' ]($ scope , $ expr , $ class );
427
+ 'notInstanceOf ' => function (Scope $ scope , Arg $ expr , Arg $ class ): ?Expr {
428
+ $ expr = $ this -> resolvers ['isInstanceOf ' ]($ scope , $ expr , $ class );
426
429
if ($ expr === null ) {
427
430
return null ;
428
431
}
@@ -438,37 +441,39 @@ private static function getExpressionResolvers(): array
438
441
[$ expr , $ class , new Arg (new ConstFetch (new Name ($ allowString ? 'true ' : 'false ' )))]
439
442
);
440
443
},
441
- 'isAnyOf ' => static function (Scope $ scope , Arg $ value , Arg $ classes ): ?Expr {
442
- return self ::buildAnyOfExpr ($ scope , $ value , $ classes , self :: $ resolvers ['isAOf ' ]);
444
+ 'isAnyOf ' => function (Scope $ scope , Arg $ value , Arg $ classes ): ?Expr {
445
+ return self ::buildAnyOfExpr ($ scope , $ value , $ classes , $ this -> resolvers ['isAOf ' ]);
443
446
},
444
- 'isNotA ' => static function (Scope $ scope , Arg $ value , Arg $ class ): Expr {
445
- return new BooleanNot (self :: $ resolvers ['isAOf ' ]($ scope , $ value , $ class ));
447
+ 'isNotA ' => function (Scope $ scope , Arg $ value , Arg $ class ): Expr {
448
+ return new BooleanNot ($ this -> resolvers ['isAOf ' ]($ scope , $ value , $ class ));
446
449
},
447
- 'implementsInterface ' => static function (Scope $ scope , Arg $ expr , Arg $ class ): ?Expr {
448
- $ classType = $ scope ->getType ($ class ->value );
449
- if (!$ classType instanceof ConstantStringType) {
450
+ 'implementsInterface ' => function (Scope $ scope , Arg $ expr , Arg $ class ): ?Expr {
451
+ $ classType = $ scope ->getType ($ class ->value )->getClassStringObjectType ();
452
+ $ classNames = $ classType ->getObjectClassNames ();
453
+
454
+ if (count ($ classNames ) !== 1 ) {
450
455
return null ;
451
456
}
452
457
453
- $ classReflection = (new ObjectType ($ classType ->getValue ()))->getClassReflection ();
454
- if ($ classReflection === null ) {
458
+ if (!$ this ->reflectionProvider ->hasClass ($ classNames [0 ])) {
455
459
return null ;
456
460
}
457
461
462
+ $ classReflection = $ this ->reflectionProvider ->getClass ($ classNames [0 ]);
458
463
if (!$ classReflection ->isInterface ()) {
459
464
return new ConstFetch (new Name ('false ' ));
460
465
}
461
466
462
- return self :: $ resolvers ['subclassOf ' ]($ scope , $ expr , $ class );
467
+ return $ this -> resolvers ['subclassOf ' ]($ scope , $ expr , $ class );
463
468
},
464
469
'keyExists ' => static function (Scope $ scope , Arg $ array , Arg $ key ): Expr {
465
470
return new FuncCall (
466
471
new Name ('array_key_exists ' ),
467
472
[$ key , $ array ]
468
473
);
469
474
},
470
- 'keyNotExists ' => static function (Scope $ scope , Arg $ array , Arg $ key ): Expr {
471
- return new BooleanNot (self :: $ resolvers ['keyExists ' ]($ scope , $ array , $ key ));
475
+ 'keyNotExists ' => function (Scope $ scope , Arg $ array , Arg $ key ): Expr {
476
+ return new BooleanNot ($ this -> resolvers ['keyExists ' ]($ scope , $ array , $ key ));
472
477
},
473
478
'validArrayKey ' => static function (Scope $ scope , Arg $ value ): Expr {
474
479
return new BooleanOr (
@@ -518,8 +523,8 @@ private static function getExpressionResolvers(): array
518
523
$ value2 ->value
519
524
);
520
525
},
521
- 'notEq ' => static function (Scope $ scope , Arg $ value , Arg $ value2 ): Expr {
522
- return new BooleanNot (self :: $ resolvers ['eq ' ]($ scope , $ value , $ value2 ));
526
+ 'notEq ' => function (Scope $ scope , Arg $ value , Arg $ value2 ): Expr {
527
+ return new BooleanNot ($ this -> resolvers ['eq ' ]($ scope , $ value , $ value2 ));
523
528
},
524
529
'same ' => static function (Scope $ scope , Arg $ value1 , Arg $ value2 ): Expr {
525
530
return new Identical (
@@ -714,8 +719,8 @@ private static function getExpressionResolvers(): array
714
719
]
715
720
);
716
721
},
717
- 'oneOf ' => static function (Scope $ scope , Arg $ needle , Arg $ array ): Expr {
718
- return self :: $ resolvers ['inArray ' ]($ scope , $ needle , $ array );
722
+ 'oneOf ' => function (Scope $ scope , Arg $ needle , Arg $ array ): Expr {
723
+ return $ this -> resolvers ['inArray ' ]($ scope , $ needle , $ array );
719
724
},
720
725
'methodExists ' => static function (Scope $ scope , Arg $ object , Arg $ method ): Expr {
721
726
return new FuncCall (
@@ -744,12 +749,12 @@ private static function getExpressionResolvers(): array
744
749
];
745
750
746
751
foreach (['contains ' , 'startsWith ' , 'endsWith ' ] as $ name ) {
747
- self :: $ resolvers [$ name ] = static function (Scope $ scope , Arg $ value , Arg $ subString ) use ($ name ): array {
752
+ $ this -> resolvers [$ name ] = function (Scope $ scope , Arg $ value , Arg $ subString ) use ($ name ): array {
748
753
if ($ scope ->getType ($ subString ->value )->isNonEmptyString ()->yes ()) {
749
754
return self ::createIsNonEmptyStringAndSomethingExprPair ($ name , [$ value , $ subString ]);
750
755
}
751
756
752
- return [self :: $ resolvers ['string ' ]($ scope , $ value ), null ];
757
+ return [$ this -> resolvers ['string ' ]($ scope , $ value ), null ];
753
758
};
754
759
}
755
760
@@ -769,14 +774,14 @@ private static function getExpressionResolvers(): array
769
774
'notWhitespaceOnly ' ,
770
775
];
771
776
foreach ($ assertionsResultingAtLeastInNonEmptyString as $ name ) {
772
- self :: $ resolvers [$ name ] = static function (Scope $ scope , Arg $ value ) use ($ name ): array {
777
+ $ this -> resolvers [$ name ] = static function (Scope $ scope , Arg $ value ) use ($ name ): array {
773
778
return self ::createIsNonEmptyStringAndSomethingExprPair ($ name , [$ value ]);
774
779
};
775
780
}
776
781
777
782
}
778
783
779
- return self :: $ resolvers ;
784
+ return $ this -> resolvers ;
780
785
}
781
786
782
787
private function handleAllNot (
@@ -797,20 +802,17 @@ static function (Type $type): Type {
797
802
798
803
if ($ methodName === 'allNotInstanceOf ' ) {
799
804
$ classType = $ scope ->getType ($ node ->getArgs ()[1 ]->value );
800
-
801
- if ($ classType instanceof ConstantStringType) {
802
- $ objectType = new ObjectType ($ classType ->getValue ());
803
- } elseif ($ classType instanceof TypeWithClassName) {
804
- $ objectType = new ObjectType ($ classType ->getClassName ());
805
- } else {
805
+ $ classNameType = $ classType ->getObjectTypeOrClassStringObjectType ();
806
+ $ classNames = $ classNameType ->getObjectClassNames ();
807
+ if (count ($ classNames ) !== 1 ) {
806
808
return new SpecifiedTypes ([], []);
807
809
}
808
810
809
811
return $ this ->allArrayOrIterable (
810
812
$ scope ,
811
813
$ node ->getArgs ()[0 ]->value ,
812
- static function (Type $ type ) use ($ objectType ): Type {
813
- return TypeCombinator::remove ($ type , $ objectType );
814
+ static function (Type $ type ) use ($ classNameType ): Type {
815
+ return TypeCombinator::remove ($ type , $ classNameType );
814
816
}
815
817
);
816
818
}
@@ -889,14 +891,15 @@ private function allArrayOrIterable(
889
891
if (count ($ arrayTypes ) > 0 ) {
890
892
$ newArrayTypes = [];
891
893
foreach ($ arrayTypes as $ arrayType ) {
892
- if ($ arrayType instanceof ConstantArrayType) {
894
+ $ constantArrays = $ arrayType ->getConstantArrays ();
895
+ if (count ($ constantArrays ) === 1 ) {
893
896
$ builder = ConstantArrayTypeBuilder::createEmpty ();
894
- foreach ($ arrayType ->getKeyTypes () as $ i => $ keyType ) {
895
- $ valueType = $ typeCallback ($ arrayType ->getValueTypes ()[$ i ]);
897
+ foreach ($ constantArrays [ 0 ] ->getKeyTypes () as $ i => $ keyType ) {
898
+ $ valueType = $ typeCallback ($ constantArrays [ 0 ] ->getValueTypes ()[$ i ]);
896
899
if ($ valueType instanceof NeverType) {
897
900
continue 2 ;
898
901
}
899
- $ builder ->setOffsetValueType ($ keyType , $ valueType , $ arrayType ->isOptionalKey ($ i ));
902
+ $ builder ->setOffsetValueType ($ keyType , $ valueType , $ constantArrays [ 0 ] ->isOptionalKey ($ i ));
900
903
}
901
904
$ newArrayTypes [] = $ builder ->getArray ();
902
905
} else {
0 commit comments