39
39
use PHPStan \Type \ArrayType ;
40
40
use PHPStan \Type \BooleanType ;
41
41
use PHPStan \Type \ConditionalTypeForParameter ;
42
+ use PHPStan \Type \Constant \ConstantArrayType ;
42
43
use PHPStan \Type \Constant \ConstantArrayTypeBuilder ;
43
44
use PHPStan \Type \Constant \ConstantBooleanType ;
44
45
use PHPStan \Type \Constant \ConstantIntegerType ;
@@ -1049,7 +1050,7 @@ private function turnListIntoConstantArray(FuncCall $countFuncCall, Type $type,
1049
1050
$ offsetType = new ConstantIntegerType ($ i );
1050
1051
$ valueTypesBuilder ->setOffsetValueType ($ offsetType , $ type ->getOffsetValueType ($ offsetType ), true );
1051
1052
}
1052
- } else {
1053
+ } elseif ( $ type -> isConstantArray ()-> yes ()) {
1053
1054
for ($ i = $ sizeType ->getMin ();; $ i ++) {
1054
1055
$ offsetType = new ConstantIntegerType ($ i );
1055
1056
$ hasOffset = $ type ->hasOffsetValueType ($ offsetType );
@@ -1060,7 +1061,11 @@ private function turnListIntoConstantArray(FuncCall $countFuncCall, Type $type,
1060
1061
}
1061
1062
1062
1063
}
1063
- return $ valueTypesBuilder ->getArray ();
1064
+
1065
+ $ arrayType = $ valueTypesBuilder ->getArray ();
1066
+ if ($ arrayType ->isIterableAtLeastOnce ()->yes ()) {
1067
+ return $ arrayType ;
1068
+ }
1064
1069
}
1065
1070
1066
1071
return null ;
@@ -1102,54 +1107,6 @@ private function specifyTypesForConstantBinaryExpression(
1102
1107
));
1103
1108
}
1104
1109
1105
- if (
1106
- !$ context ->null ()
1107
- && $ exprNode instanceof FuncCall
1108
- && count ($ exprNode ->getArgs ()) >= 1
1109
- && $ exprNode ->name instanceof Name
1110
- && in_array (strtolower ((string ) $ exprNode ->name ), ['count ' , 'sizeof ' ], true )
1111
- && $ constantType instanceof ConstantIntegerType
1112
- ) {
1113
- if ($ constantType ->getValue () < 0 ) {
1114
- return $ this ->create ($ exprNode ->getArgs ()[0 ]->value , new NeverType (), $ context , false , $ scope , $ rootExpr );
1115
- }
1116
-
1117
- $ argType = $ scope ->getType ($ exprNode ->getArgs ()[0 ]->value );
1118
-
1119
- if ($ argType instanceof UnionType) {
1120
- $ narrowed = $ this ->narrowUnionByArraySize ($ exprNode , $ argType , $ constantType , $ context , $ scope , $ rootExpr );
1121
- if ($ narrowed !== null ) {
1122
- return $ narrowed ;
1123
- }
1124
- }
1125
-
1126
- if ($ context ->truthy () || $ constantType ->getValue () === 0 ) {
1127
- $ newContext = $ context ;
1128
- if ($ constantType ->getValue () === 0 ) {
1129
- $ newContext = $ newContext ->negate ();
1130
- }
1131
-
1132
- if ($ argType ->isArray ()->yes ()) {
1133
- if (
1134
- $ context ->truthy ()
1135
- && $ argType ->isConstantArray ()->yes ()
1136
- && $ constantType ->isSuperTypeOf ($ argType ->getArraySize ())->no ()
1137
- ) {
1138
- return $ this ->create ($ exprNode ->getArgs ()[0 ]->value , new NeverType (), $ context , false , $ scope , $ rootExpr );
1139
- }
1140
-
1141
- $ funcTypes = $ this ->create ($ exprNode , $ constantType , $ context , false , $ scope , $ rootExpr );
1142
- $ constArray = $ this ->turnListIntoConstantArray ($ exprNode , $ argType , $ constantType , $ scope );
1143
- if ($ context ->truthy () && $ constArray !== null ) {
1144
- $ valueTypes = $ this ->create ($ exprNode ->getArgs ()[0 ]->value , $ constArray , $ context , false , $ scope , $ rootExpr );
1145
- } else {
1146
- $ valueTypes = $ this ->create ($ exprNode ->getArgs ()[0 ]->value , new NonEmptyArrayType (), $ newContext , false , $ scope , $ rootExpr );
1147
- }
1148
- return $ funcTypes ->unionWith ($ valueTypes );
1149
- }
1150
- }
1151
- }
1152
-
1153
1110
if (
1154
1111
!$ context ->null ()
1155
1112
&& $ exprNode instanceof FuncCall
@@ -1200,41 +1157,6 @@ private function specifyTypesForConstantStringBinaryExpression(
1200
1157
}
1201
1158
$ constantStringValue = $ scalarValues [0 ];
1202
1159
1203
- if (
1204
- $ context ->truthy ()
1205
- && $ exprNode instanceof FuncCall
1206
- && $ exprNode ->name instanceof Name
1207
- && in_array (strtolower ($ exprNode ->name ->toString ()), [
1208
- 'substr ' , 'strstr ' , 'stristr ' , 'strchr ' , 'strrchr ' , 'strtolower ' , 'strtoupper ' , 'ucfirst ' , 'lcfirst ' ,
1209
- 'mb_substr ' , 'mb_strstr ' , 'mb_stristr ' , 'mb_strchr ' , 'mb_strrchr ' , 'mb_strtolower ' , 'mb_strtoupper ' , 'mb_ucfirst ' , 'mb_lcfirst ' ,
1210
- 'ucwords ' , 'mb_convert_case ' , 'mb_convert_kana ' ,
1211
- ], true )
1212
- && isset ($ exprNode ->getArgs ()[0 ])
1213
- && $ constantStringValue !== ''
1214
- ) {
1215
- $ argType = $ scope ->getType ($ exprNode ->getArgs ()[0 ]->value );
1216
-
1217
- if ($ argType ->isString ()->yes ()) {
1218
- if ($ constantStringValue !== '0 ' ) {
1219
- return $ this ->create (
1220
- $ exprNode ->getArgs ()[0 ]->value ,
1221
- TypeCombinator::intersect ($ argType , new AccessoryNonFalsyStringType ()),
1222
- $ context ,
1223
- false ,
1224
- $ scope ,
1225
- );
1226
- }
1227
-
1228
- return $ this ->create (
1229
- $ exprNode ->getArgs ()[0 ]->value ,
1230
- TypeCombinator::intersect ($ argType , new AccessoryNonEmptyStringType ()),
1231
- $ context ,
1232
- false ,
1233
- $ scope ,
1234
- );
1235
- }
1236
- }
1237
-
1238
1160
if (
1239
1161
$ exprNode instanceof FuncCall
1240
1162
&& $ exprNode ->name instanceof Name
@@ -2137,6 +2059,70 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2137
2059
}
2138
2060
$ rightType = $ scope ->getType ($ rightExpr );
2139
2061
2062
+ if (
2063
+ !$ context ->null ()
2064
+ && $ unwrappedLeftExpr instanceof FuncCall
2065
+ && count ($ unwrappedLeftExpr ->getArgs ()) >= 1
2066
+ && $ unwrappedLeftExpr ->name instanceof Name
2067
+ && in_array (strtolower ((string ) $ unwrappedLeftExpr ->name ), ['count ' , 'sizeof ' ], true )
2068
+ && $ rightType ->isInteger ()->yes ()
2069
+ ) {
2070
+ if (IntegerRangeType::fromInterval (null , -1 )->isSuperTypeOf ($ rightType )->yes ()) {
2071
+ return $ this ->create ($ unwrappedLeftExpr ->getArgs ()[0 ]->value , new NeverType (), $ context , false , $ scope , $ rootExpr );
2072
+ }
2073
+
2074
+ $ argType = $ scope ->getType ($ unwrappedLeftExpr ->getArgs ()[0 ]->value );
2075
+ $ isZero = (new ConstantIntegerType (0 ))->isSuperTypeOf ($ rightType );
2076
+ if ($ isZero ->yes ()) {
2077
+ $ funcTypes = $ this ->create ($ unwrappedLeftExpr , $ rightType , $ context , false , $ scope , $ rootExpr );
2078
+
2079
+ if ($ context ->truthy () && !$ argType ->isArray ()->yes ()) {
2080
+ $ newArgType = new UnionType ([
2081
+ new ObjectType (Countable::class),
2082
+ new ConstantArrayType ([], []),
2083
+ ]);
2084
+ } else {
2085
+ $ newArgType = new ConstantArrayType ([], []);
2086
+ }
2087
+
2088
+ return $ funcTypes ->unionWith (
2089
+ $ this ->create ($ unwrappedLeftExpr ->getArgs ()[0 ]->value , $ newArgType , $ context , false , $ scope , $ rootExpr ),
2090
+ );
2091
+ }
2092
+
2093
+ if ($ argType instanceof UnionType) {
2094
+ $ narrowed = $ this ->narrowUnionByArraySize ($ unwrappedLeftExpr , $ argType , $ rightType , $ context , $ scope , $ rootExpr );
2095
+ if ($ narrowed !== null ) {
2096
+ return $ narrowed ;
2097
+ }
2098
+ }
2099
+
2100
+ if ($ context ->truthy ()) {
2101
+ if ($ argType ->isArray ()->yes ()) {
2102
+ if (
2103
+ $ argType ->isConstantArray ()->yes ()
2104
+ && $ rightType ->isSuperTypeOf ($ argType ->getArraySize ())->no ()
2105
+ ) {
2106
+ return $ this ->create ($ unwrappedLeftExpr ->getArgs ()[0 ]->value , new NeverType (), $ context , false , $ scope , $ rootExpr );
2107
+ }
2108
+
2109
+ $ funcTypes = $ this ->create ($ unwrappedLeftExpr , $ rightType , $ context , false , $ scope , $ rootExpr );
2110
+ $ constArray = $ this ->turnListIntoConstantArray ($ unwrappedLeftExpr , $ argType , $ rightType , $ scope );
2111
+ if ($ constArray !== null ) {
2112
+ return $ funcTypes ->unionWith (
2113
+ $ this ->create ($ unwrappedLeftExpr ->getArgs ()[0 ]->value , $ constArray , $ context , false , $ scope , $ rootExpr ),
2114
+ );
2115
+ } elseif (IntegerRangeType::fromInterval (1 , null )->isSuperTypeOf ($ rightType )->yes ()) {
2116
+ return $ funcTypes ->unionWith (
2117
+ $ this ->create ($ unwrappedLeftExpr ->getArgs ()[0 ]->value , new NonEmptyArrayType (), $ context , false , $ scope , $ rootExpr ),
2118
+ );
2119
+ }
2120
+
2121
+ return $ funcTypes ;
2122
+ }
2123
+ }
2124
+ }
2125
+
2140
2126
if (
2141
2127
$ context ->true ()
2142
2128
&& $ unwrappedLeftExpr instanceof FuncCall
@@ -2171,6 +2157,41 @@ public function resolveIdentical(Expr\BinaryOp\Identical $expr, Scope $scope, Ty
2171
2157
}
2172
2158
}
2173
2159
2160
+ if (
2161
+ $ context ->truthy ()
2162
+ && $ unwrappedLeftExpr instanceof FuncCall
2163
+ && $ unwrappedLeftExpr ->name instanceof Name
2164
+ && in_array (strtolower ($ unwrappedLeftExpr ->name ->toString ()), [
2165
+ 'substr ' , 'strstr ' , 'stristr ' , 'strchr ' , 'strrchr ' , 'strtolower ' , 'strtoupper ' , 'ucfirst ' , 'lcfirst ' ,
2166
+ 'mb_substr ' , 'mb_strstr ' , 'mb_stristr ' , 'mb_strchr ' , 'mb_strrchr ' , 'mb_strtolower ' , 'mb_strtoupper ' , 'mb_ucfirst ' , 'mb_lcfirst ' ,
2167
+ 'ucwords ' , 'mb_convert_case ' , 'mb_convert_kana ' ,
2168
+ ], true )
2169
+ && isset ($ unwrappedLeftExpr ->getArgs ()[0 ])
2170
+ && $ rightType ->isNonEmptyString ()->yes ()
2171
+ ) {
2172
+ $ argType = $ scope ->getType ($ unwrappedLeftExpr ->getArgs ()[0 ]->value );
2173
+
2174
+ if ($ argType ->isString ()->yes ()) {
2175
+ if ($ rightType ->isNonFalsyString ()->yes ()) {
2176
+ return $ this ->create (
2177
+ $ unwrappedLeftExpr ->getArgs ()[0 ]->value ,
2178
+ TypeCombinator::intersect ($ argType , new AccessoryNonFalsyStringType ()),
2179
+ $ context ,
2180
+ false ,
2181
+ $ scope ,
2182
+ );
2183
+ }
2184
+
2185
+ return $ this ->create (
2186
+ $ unwrappedLeftExpr ->getArgs ()[0 ]->value ,
2187
+ TypeCombinator::intersect ($ argType , new AccessoryNonEmptyStringType ()),
2188
+ $ context ,
2189
+ false ,
2190
+ $ scope ,
2191
+ );
2192
+ }
2193
+ }
2194
+
2174
2195
if ($ rightType ->isInteger ()->yes () || $ rightType ->isString ()->yes ()) {
2175
2196
$ types = null ;
2176
2197
foreach ($ rightType ->getFiniteTypes () as $ finiteType ) {
0 commit comments