5
5
use PhpParser \Node \Expr \FuncCall ;
6
6
use PHPStan \Analyser \Scope ;
7
7
use PHPStan \Reflection \FunctionReflection ;
8
+ use PHPStan \Reflection \ParametersAcceptorSelector ;
9
+ use PHPStan \Type \Accessory \AccessoryArrayListType ;
10
+ use PHPStan \Type \Accessory \NonEmptyArrayType ;
8
11
use PHPStan \Type \ArrayType ;
12
+ use PHPStan \Type \Constant \ConstantBooleanType ;
9
13
use PHPStan \Type \DynamicFunctionReturnTypeExtension ;
10
- use PHPStan \Type \IntegerType ;
14
+ use PHPStan \Type \NeverType ;
11
15
use PHPStan \Type \StringType ;
12
16
use PHPStan \Type \Type ;
17
+ use PHPStan \Type \TypeCombinator ;
18
+ use PHPStan \Type \UnionType ;
19
+ use function count ;
13
20
14
21
final class MbConvertEncodingFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
15
22
{
@@ -30,16 +37,54 @@ public function getTypeFromFunctionCall(
30
37
}
31
38
32
39
$ argType = $ scope ->getType ($ functionCall ->getArgs ()[0 ]->value );
33
- $ isString = $ argType ->isString ();
34
- $ isArray = $ argType ->isArray ();
35
- $ compare = $ isString ->compareTo ($ isArray );
36
- if ($ compare === $ isString ) {
40
+
41
+ $ initialReturnType = ParametersAcceptorSelector::selectFromArgs (
42
+ $ scope ,
43
+ $ functionCall ->getArgs (),
44
+ $ functionReflection ->getVariants (),
45
+ )->getReturnType ();
46
+
47
+ $ result = TypeCombinator::intersect ($ initialReturnType , $ this ->generalizeStringType ($ argType ));
48
+ if ($ result instanceof NeverType) {
49
+ return null ;
50
+ }
51
+
52
+ return TypeCombinator::union ($ result , new ConstantBooleanType (false ));
53
+ }
54
+
55
+ public function generalizeStringType (Type $ type ): Type
56
+ {
57
+ if ($ type instanceof UnionType) {
58
+ return $ type ->traverse ([$ this , 'generalizeStringType ' ]);
59
+ }
60
+
61
+ if ($ type ->isString ()->yes ()) {
37
62
return new StringType ();
38
- } elseif ($ compare === $ isArray ) {
39
- return new ArrayType (new IntegerType (), new StringType ());
40
63
}
41
64
42
- return null ;
65
+ $ constantArrays = $ type ->getConstantArrays ();
66
+ if (count ($ constantArrays ) > 0 ) {
67
+ $ types = [];
68
+ foreach ($ constantArrays as $ constantArray ) {
69
+ $ types [] = $ constantArray ->traverse ([$ this , 'generalizeStringType ' ]);
70
+ }
71
+
72
+ return TypeCombinator::union (...$ types );
73
+ }
74
+
75
+ if ($ type ->isArray ()->yes ()) {
76
+ $ newArrayType = new ArrayType ($ type ->getIterableKeyType (), $ this ->generalizeStringType ($ type ->getIterableValueType ()));
77
+ if ($ type ->isIterableAtLeastOnce ()->yes ()) {
78
+ $ newArrayType = TypeCombinator::intersect ($ newArrayType , new NonEmptyArrayType ());
79
+ }
80
+ if ($ type ->isList ()->yes ()) {
81
+ $ newArrayType = TypeCombinator::intersect ($ newArrayType , new AccessoryArrayListType ());
82
+ }
83
+
84
+ return $ newArrayType ;
85
+ }
86
+
87
+ return $ type ;
43
88
}
44
89
45
90
}
0 commit comments