55use PhpParser \Node \Expr \FuncCall ;
66use PHPStan \Analyser \Scope ;
77use PHPStan \Reflection \FunctionReflection ;
8+ use PHPStan \Reflection \ParametersAcceptorSelector ;
9+ use PHPStan \Type \Accessory \AccessoryArrayListType ;
10+ use PHPStan \Type \Accessory \NonEmptyArrayType ;
811use PHPStan \Type \ArrayType ;
12+ use PHPStan \Type \Constant \ConstantBooleanType ;
913use PHPStan \Type \DynamicFunctionReturnTypeExtension ;
10- use PHPStan \Type \IntegerType ;
14+ use PHPStan \Type \NeverType ;
1115use PHPStan \Type \StringType ;
1216use PHPStan \Type \Type ;
17+ use PHPStan \Type \TypeCombinator ;
18+ use PHPStan \Type \UnionType ;
19+ use function count ;
1320
1421final class MbConvertEncodingFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
1522{
@@ -30,16 +37,54 @@ public function getTypeFromFunctionCall(
3037 }
3138
3239 $ 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 ()) {
3762 return new StringType ();
38- } elseif ($ compare === $ isArray ) {
39- return new ArrayType (new IntegerType (), new StringType ());
4063 }
4164
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 ;
4388 }
4489
4590}
0 commit comments