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 ;
1319
1420final class MbConvertEncodingFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
1521{
@@ -30,16 +36,54 @@ public function getTypeFromFunctionCall(
3036 }
3137
3238 $ argType = $ scope ->getType ($ functionCall ->getArgs ()[0 ]->value );
33- $ isString = $ argType ->isString ();
34- $ isArray = $ argType ->isArray ();
35- $ compare = $ isString ->compareTo ($ isArray );
36- if ($ compare === $ isString ) {
39+
40+ $ initialReturnType = ParametersAcceptorSelector::selectFromArgs (
41+ $ scope ,
42+ $ functionCall ->getArgs (),
43+ $ functionReflection ->getVariants (),
44+ )->getReturnType ();
45+
46+ $ result = TypeCombinator::intersect ($ initialReturnType , $ this ->generalizeStringType ($ argType ));
47+ if ($ result instanceof NeverType) {
48+ return null ;
49+ }
50+
51+ return TypeCombinator::union ($ result , new ConstantBooleanType (false ));
52+ }
53+
54+ public function generalizeStringType (Type $ type ): Type
55+ {
56+ if ($ type instanceof UnionType) {
57+ return $ type ->traverse ([$ this , 'generalizeStringType ' ]);
58+ }
59+
60+ if ($ type ->isString ()->yes ()) {
3761 return new StringType ();
38- } elseif ($ compare === $ isArray ) {
39- return new ArrayType (new IntegerType (), new StringType ());
4062 }
4163
42- return null ;
64+ $ constantArrays = $ type ->getConstantArrays ();
65+ if (count ($ constantArrays ) > 0 ) {
66+ $ types = [];
67+ foreach ($ constantArrays as $ constantArray ) {
68+ $ types [] = $ constantArray ->traverse ([$ this , 'generalizeStringType ' ]);
69+ }
70+
71+ return TypeCombinator::union (...$ types );
72+ }
73+
74+ if ($ type ->isArray ()->yes ()) {
75+ $ newArrayType = new ArrayType ($ type ->getIterableKeyType (), $ this ->generalizeStringType ($ type ->getIterableValueType ()));
76+ if ($ type ->isIterableAtLeastOnce ()->yes ()) {
77+ $ newArrayType = TypeCombinator::intersect ($ newArrayType , new NonEmptyArrayType ());
78+ }
79+ if ($ type ->isList ()->yes ()) {
80+ $ newArrayType = TypeCombinator::intersect ($ newArrayType , new AccessoryArrayListType ());
81+ }
82+
83+ return $ newArrayType ;
84+ }
85+
86+ return $ type ;
4387 }
4488
4589}
0 commit comments