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,54 @@ 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+ $ result = TypeCombinator::intersect ($ initialReturnType , $ this ->generalizeStringType ($ argType ));
53+ if ($ result instanceof NeverType) {
54+ return null ;
55+ }
56+
57+ return TypeCombinator::union ($ result , new ConstantBooleanType (false ));
58+ }
59+
60+ private function generalizeStringType (Type $ type ): Type
61+ {
62+ if ($ type instanceof UnionType) {
63+ return $ type ->traverse ([$ this , 'generalizeStringType ' ]);
64+ }
65+
66+ if ($ type ->isString ()->yes ()) {
3767 return new StringType ();
38- } elseif ($ compare === $ isArray ) {
39- return new ArrayType (new IntegerType (), new StringType ());
4068 }
4169
42- return null ;
70+ $ constantArrays = $ type ->getConstantArrays ();
71+ if (count ($ constantArrays ) > 0 ) {
72+ $ types = [];
73+ foreach ($ constantArrays as $ constantArray ) {
74+ $ types [] = $ constantArray ->traverse ([$ this , 'generalizeStringType ' ]);
75+ }
76+
77+ return TypeCombinator::union (...$ types );
78+ }
79+
80+ if ($ type ->isArray ()->yes ()) {
81+ $ newArrayType = new ArrayType ($ type ->getIterableKeyType (), $ this ->generalizeStringType ($ type ->getIterableValueType ()));
82+ if ($ type ->isIterableAtLeastOnce ()->yes ()) {
83+ $ newArrayType = TypeCombinator::intersect ($ newArrayType , new NonEmptyArrayType ());
84+ }
85+ if ($ type ->isList ()->yes ()) {
86+ $ newArrayType = TypeCombinator::intersect ($ newArrayType , new AccessoryArrayListType ());
87+ }
88+
89+ return $ newArrayType ;
90+ }
91+
92+ return $ type ;
4393 }
4494
4595}
0 commit comments