2
2
3
3
namespace PHPStan \Type \Php ;
4
4
5
- use PhpParser \Node ;
6
- use PhpParser \Node \Expr \ClassConstFetch ;
7
5
use PhpParser \Node \Expr \FuncCall ;
8
- use PhpParser \Node \Name ;
9
6
use PHPStan \Analyser \Scope ;
10
7
use PHPStan \Analyser \SpecifiedTypes ;
11
8
use PHPStan \Analyser \TypeSpecifier ;
12
9
use PHPStan \Analyser \TypeSpecifierAwareExtension ;
13
10
use PHPStan \Analyser \TypeSpecifierContext ;
14
11
use PHPStan \Reflection \FunctionReflection ;
15
- use PHPStan \ShouldNotHappenException ;
16
12
use PHPStan \Type \ClassStringType ;
17
13
use PHPStan \Type \Constant \ConstantBooleanType ;
18
14
use PHPStan \Type \Constant \ConstantStringType ;
19
15
use PHPStan \Type \FunctionTypeSpecifyingExtension ;
20
16
use PHPStan \Type \Generic \GenericClassStringType ;
17
+ use PHPStan \Type \IntersectionType ;
21
18
use PHPStan \Type \ObjectType ;
22
19
use PHPStan \Type \ObjectWithoutClassType ;
20
+ use PHPStan \Type \Type ;
21
+ use PHPStan \Type \TypeCombinator ;
22
+ use PHPStan \Type \TypeTraverser ;
23
+ use PHPStan \Type \UnionType ;
24
+ use function count ;
23
25
use function strtolower ;
24
26
25
27
class IsAFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension
@@ -30,53 +32,60 @@ class IsAFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtens
30
32
public function isFunctionSupported (FunctionReflection $ functionReflection , FuncCall $ node , TypeSpecifierContext $ context ): bool
31
33
{
32
34
return strtolower ($ functionReflection ->getName ()) === 'is_a '
33
- && isset ($ node ->getArgs ()[0 ])
34
- && isset ($ node ->getArgs ()[1 ])
35
35
&& !$ context ->null ();
36
36
}
37
37
38
38
public function specifyTypes (FunctionReflection $ functionReflection , FuncCall $ node , Scope $ scope , TypeSpecifierContext $ context ): SpecifiedTypes
39
39
{
40
- if ($ context -> null () ) {
41
- throw new ShouldNotHappenException ();
40
+ if (count ( $ node -> getArgs ()) < 2 ) {
41
+ return new SpecifiedTypes ();
42
42
}
43
+ $ classType = $ scope ->getType ($ node ->getArgs ()[1 ]->value );
44
+ $ allowStringType = isset ($ node ->getArgs ()[2 ]) ? $ scope ->getType ($ node ->getArgs ()[2 ]->value ) : new ConstantBooleanType (false );
45
+ $ allowString = !$ allowStringType ->equals (new ConstantBooleanType (false ));
43
46
44
- $ classNameArgExpr = $ node ->getArgs ()[1 ]->value ;
45
- $ classNameArgExprType = $ scope ->getType ($ classNameArgExpr );
46
- if (
47
- $ classNameArgExpr instanceof ClassConstFetch
48
- && $ classNameArgExpr ->class instanceof Name
49
- && $ classNameArgExpr ->name instanceof Node \Identifier
50
- && strtolower ($ classNameArgExpr ->name ->name ) === 'class '
51
- ) {
52
- $ objectType = $ scope ->resolveTypeByName ($ classNameArgExpr ->class );
53
- $ types = $ this ->typeSpecifier ->create ($ node ->getArgs ()[0 ]->value , $ objectType , $ context , false , $ scope );
54
- } elseif ($ classNameArgExprType instanceof ConstantStringType) {
55
- $ objectType = new ObjectType ($ classNameArgExprType ->getValue ());
56
- $ types = $ this ->typeSpecifier ->create ($ node ->getArgs ()[0 ]->value , $ objectType , $ context , false , $ scope );
57
- } elseif ($ classNameArgExprType instanceof GenericClassStringType) {
58
- $ objectType = $ classNameArgExprType ->getGenericType ();
59
- $ types = $ this ->typeSpecifier ->create ($ node ->getArgs ()[0 ]->value , $ objectType , $ context , false , $ scope );
60
- } elseif ($ context ->true ()) {
61
- $ objectType = new ObjectWithoutClassType ();
62
- $ types = $ this ->typeSpecifier ->create ($ node ->getArgs ()[0 ]->value , $ objectType , $ context , false , $ scope );
63
- } else {
64
- $ types = new SpecifiedTypes ();
47
+ if (!$ classType instanceof ConstantStringType && !$ context ->truthy ()) {
48
+ return new SpecifiedTypes ([], []);
65
49
}
66
50
67
- if (isset ($ node ->getArgs ()[2 ]) && $ context ->true ()) {
68
- if (!$ scope ->getType ($ node ->getArgs ()[2 ]->value )->isSuperTypeOf (new ConstantBooleanType (true ))->no ()) {
69
- $ types = $ types ->intersectWith ($ this ->typeSpecifier ->create (
70
- $ node ->getArgs ()[0 ]->value ,
71
- isset ($ objectType ) ? new GenericClassStringType ($ objectType ) : new ClassStringType (),
72
- $ context ,
73
- false ,
74
- $ scope ,
75
- ));
51
+ $ type = TypeTraverser::map ($ classType , static function (Type $ type , callable $ traverse ) use ($ allowString ): Type {
52
+ if ($ type instanceof UnionType || $ type instanceof IntersectionType) {
53
+ return $ traverse ($ type );
76
54
}
77
- }
55
+ if ($ type instanceof ConstantStringType) {
56
+ if ($ allowString ) {
57
+ return TypeCombinator::union (
58
+ new ObjectType ($ type ->getValue ()),
59
+ new GenericClassStringType (new ObjectType ($ type ->getValue ())),
60
+ );
61
+ }
62
+ return new ObjectType ($ type ->getValue ());
63
+ }
64
+ if ($ type instanceof GenericClassStringType) {
65
+ if ($ allowString ) {
66
+ return TypeCombinator::union (
67
+ $ type ->getGenericType (),
68
+ $ type ,
69
+ );
70
+ }
71
+ return $ type ->getGenericType ();
72
+ }
73
+ if ($ allowString ) {
74
+ return TypeCombinator::union (
75
+ new ObjectWithoutClassType (),
76
+ new ClassStringType (),
77
+ );
78
+ }
79
+ return new ObjectWithoutClassType ();
80
+ });
78
81
79
- return $ types ;
82
+ return $ this ->typeSpecifier ->create (
83
+ $ node ->getArgs ()[0 ]->value ,
84
+ $ type ,
85
+ $ context ,
86
+ false ,
87
+ $ scope ,
88
+ );
80
89
}
81
90
82
91
public function setTypeSpecifier (TypeSpecifier $ typeSpecifier ): void
0 commit comments