55use PhpParser \Node \Expr \FuncCall ;
66use PHPStan \Analyser \Scope ;
77use PHPStan \Reflection \FunctionReflection ;
8+ use PHPStan \Reflection \ParametersAcceptorSelector ;
9+ use PHPStan \TrinaryLogic ;
10+ use PHPStan \Type \Accessory \AccessoryArrayListType ;
11+ use PHPStan \Type \Accessory \NonEmptyArrayType ;
812use PHPStan \Type \ArrayType ;
13+ use PHPStan \Type \Constant \ConstantArrayType ;
14+ use PHPStan \Type \Constant \ConstantBooleanType ;
915use PHPStan \Type \DynamicFunctionReturnTypeExtension ;
1016use PHPStan \Type \IntegerType ;
17+ use PHPStan \Type \MixedType ;
18+ use PHPStan \Type \NeverType ;
1119use PHPStan \Type \StringType ;
1220use PHPStan \Type \Type ;
21+ use PHPStan \Type \TypeCombinator ;
22+ use PHPStan \Type \TypeTraverser ;
23+ use PHPStan \Type \UnionType ;
24+ use PHPStan \Type \VerbosityLevel ;
1325
1426final class MbConvertEncodingFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
1527{
@@ -30,16 +42,67 @@ public function getTypeFromFunctionCall(
3042 }
3143
3244 $ argType = $ scope ->getType ($ functionCall ->getArgs ()[0 ]->value );
33- $ isString = $ argType ->isString ();
34- $ isArray = $ argType ->isArray ();
35- $ compare = $ isString ->compareTo ($ isArray );
36- if ($ compare === $ isString ) {
45+
46+ $ initialReturnType = ParametersAcceptorSelector::selectFromArgs (
47+ $ scope ,
48+ $ functionCall ->getArgs (),
49+ $ functionReflection ->getVariants (),
50+ )->getReturnType ();
51+
52+ $ computedReturnType = $ this ->generalizeStringType ($ argType );
53+
54+ $ result = TypeCombinator::intersect (
55+ $ initialReturnType ,
56+ TypeCombinator::union ($ computedReturnType , new ConstantBooleanType (false ))
57+ );
58+ if ($ result instanceof NeverType) {
59+ return null ;
60+ }
61+
62+ return $ result ;
63+ }
64+
65+ private function generalizeStringType (Type $ type ): Type
66+ {
67+ if ($ type instanceof UnionType) {
68+ return $ type ->traverse ($ this ->generalizeStringType (...));
69+ }
70+
71+ if ($ type ->isString ()->yes ()) {
3772 return new StringType ();
38- } elseif ($ compare === $ isArray ) {
39- return new ArrayType (new IntegerType (), new StringType ());
4073 }
4174
42- return null ;
75+ $ constantArrays = $ type ->getConstantArrays ();
76+ if (count ($ constantArrays ) > 0 ) {
77+ $ types = [];
78+ foreach ($ constantArrays as $ constantArray ) {
79+ $ c = new ConstantArrayType (
80+ $ constantArray ->getKeyTypes (),
81+ array_map ($ this ->generalizeStringType (...), $ constantArray ->getValueTypes ()),
82+ $ constantArray ->getNextAutoIndexes (),
83+ $ constantArray ->getOptionalKeys (),
84+ $ constantArray ->isList (),
85+ );
86+
87+ $ types [] = $ c ;
88+ }
89+
90+ return TypeCombinator::union (...$ types );
91+ }
92+
93+ if ($ type ->isArray ()->yes ()) {
94+ $ newArrayType = new ArrayType ($ type ->getIterableKeyType (), $ this ->generalizeStringType ($ type ->getIterableValueType ()));
95+ if ($ type ->isIterableAtLeastOnce ()->yes ()) {
96+ $ newArrayType = TypeCombinator::intersect ($ newArrayType , new NonEmptyArrayType ());
97+ }
98+ if ($ type ->isList ()->yes ()) {
99+ $ newArrayType = TypeCombinator::intersect ($ newArrayType , new AccessoryArrayListType ());
100+ }
101+
102+ return $ newArrayType ;
103+ }
104+
105+ return $ type ;
43106 }
44107
45108}
0 commit comments