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,59 @@ 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+ $ types [] = $ constantArray ->traverse ($ this ->generalizeStringType (...));
80+ }
81+
82+ return TypeCombinator::union (...$ types );
83+ }
84+
85+ if ($ type ->isArray ()->yes ()) {
86+ $ newArrayType = new ArrayType ($ type ->getIterableKeyType (), $ this ->generalizeStringType ($ type ->getIterableValueType ()));
87+ if ($ type ->isIterableAtLeastOnce ()->yes ()) {
88+ $ newArrayType = TypeCombinator::intersect ($ newArrayType , new NonEmptyArrayType ());
89+ }
90+ if ($ type ->isList ()->yes ()) {
91+ $ newArrayType = TypeCombinator::intersect ($ newArrayType , new AccessoryArrayListType ());
92+ }
93+
94+ return $ newArrayType ;
95+ }
96+
97+ return $ type ;
4398 }
4499
45100}
0 commit comments