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 ;
1118use PHPStan \Type \StringType ;
1219use PHPStan \Type \Type ;
20+ use PHPStan \Type \TypeCombinator ;
21+ use PHPStan \Type \TypeTraverser ;
22+ use PHPStan \Type \UnionType ;
23+ use PHPStan \Type \VerbosityLevel ;
1324
1425final class MbConvertEncodingFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
1526{
@@ -30,16 +41,62 @@ public function getTypeFromFunctionCall(
3041 }
3142
3243 $ argType = $ scope ->getType ($ functionCall ->getArgs ()[0 ]->value );
33- $ isString = $ argType ->isString ();
34- $ isArray = $ argType ->isArray ();
35- $ compare = $ isString ->compareTo ($ isArray );
36- if ($ compare === $ isString ) {
44+
45+ $ initialReturnType = ParametersAcceptorSelector::selectFromArgs (
46+ $ scope ,
47+ $ functionCall ->getArgs (),
48+ $ functionReflection ->getVariants (),
49+ )->getReturnType ();
50+
51+ $ computedReturnType = $ this ->generalizeStringType ($ argType );
52+
53+ return TypeCombinator::intersect (
54+ $ initialReturnType ,
55+ TypeCombinator::union ($ computedReturnType , new ConstantBooleanType (false ))
56+ );
57+ }
58+
59+ private function generalizeStringType (Type $ type ): Type
60+ {
61+ if ($ type instanceof UnionType) {
62+ return $ type ->traverse ($ this ->generalizeStringType (...));
63+ }
64+
65+ if ($ type ->isString ()->yes ()) {
3766 return new StringType ();
38- } elseif ($ compare === $ isArray ) {
39- return new ArrayType (new IntegerType (), new StringType ());
4067 }
4168
42- return null ;
69+ $ constantArrays = $ type ->getConstantArrays ();
70+ if (count ($ constantArrays ) > 0 ) {
71+ $ types = [];
72+ foreach ($ constantArrays as $ constantArray ) {
73+ $ c = new ConstantArrayType (
74+ $ constantArray ->getKeyTypes (),
75+ array_map ($ this ->generalizeStringType (...), $ constantArray ->getValueTypes ()),
76+ $ constantArray ->getNextAutoIndexes (),
77+ $ constantArray ->getOptionalKeys (),
78+ $ constantArray ->isList (),
79+ );
80+
81+ $ types [] = $ c ;
82+ }
83+
84+ return TypeCombinator::union (...$ types );
85+ }
86+
87+ if ($ type ->isArray ()->yes ()) {
88+ $ newArrayType = new ArrayType ($ type ->getIterableKeyType (), $ this ->generalizeStringType ($ type ->getIterableValueType ()));
89+ if ($ type ->isIterableAtLeastOnce ()->yes ()) {
90+ $ newArrayType = TypeCombinator::intersect ($ newArrayType , new NonEmptyArrayType ());
91+ }
92+ if ($ type ->isList ()->yes ()) {
93+ $ newArrayType = TypeCombinator::intersect ($ newArrayType , new AccessoryArrayListType ());
94+ }
95+
96+ return $ newArrayType ;
97+ }
98+
99+ return $ type ;
43100 }
44101
45102}
0 commit comments