31
31
use PHPStan \Type \CompoundType ;
32
32
use PHPStan \Type \ConstantScalarType ;
33
33
use PHPStan \Type \ConstantType ;
34
+ use PHPStan \Type \ConstantTypeHelper ;
34
35
use PHPStan \Type \ErrorType ;
35
36
use PHPStan \Type \GeneralizePrecision ;
36
37
use PHPStan \Type \Generic \TemplateTypeMap ;
39
40
use PHPStan \Type \IntersectionType ;
40
41
use PHPStan \Type \MixedType ;
41
42
use PHPStan \Type \NeverType ;
43
+ use PHPStan \Type \NullType ;
42
44
use PHPStan \Type \Type ;
43
45
use PHPStan \Type \TypeCombinator ;
44
46
use PHPStan \Type \UnionType ;
@@ -812,7 +814,7 @@ public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type
812
814
813
815
$ keyTypesCount = count ($ this ->keyTypes );
814
816
for ($ i = 0 ; $ i < $ keyTypesCount ; $ i += $ length ) {
815
- $ chunk = $ this ->slice ( $ i , $ length, true );
817
+ $ chunk = $ this ->sliceArray ( new ConstantIntegerType ( $ i ), new ConstantIntegerType ( $ length), TrinaryLogic:: createYes () );
816
818
$ builder ->setOffsetValueType (null , $ preserveKeys ->yes () ? $ chunk : $ chunk ->getValuesArray ());
817
819
}
818
820
@@ -882,7 +884,7 @@ public function popArray(): Type
882
884
return $ this ->removeLastElements (1 );
883
885
}
884
886
885
- private function reverseConstantArray (TrinaryLogic $ preserveKeys ): self
887
+ public function reverseArray (TrinaryLogic $ preserveKeys ): Type
886
888
{
887
889
$ keyTypesReversed = array_reverse ($ this ->keyTypes , true );
888
890
$ keyTypes = array_values ($ keyTypesReversed );
@@ -894,11 +896,6 @@ private function reverseConstantArray(TrinaryLogic $preserveKeys): self
894
896
return $ preserveKeys ->yes () ? $ reversed : $ reversed ->reindex ();
895
897
}
896
898
897
- public function reverseArray (TrinaryLogic $ preserveKeys ): Type
898
- {
899
- return $ this ->reverseConstantArray ($ preserveKeys );
900
- }
901
-
902
899
public function searchArray (Type $ needleType ): Type
903
900
{
904
901
$ matches = [];
@@ -957,6 +954,85 @@ public function shuffleArray(): Type
957
954
return $ generalizedArray ;
958
955
}
959
956
957
+ public function sliceArray (Type $ offsetType , Type $ lengthType , TrinaryLogic $ preserveKeys ): Type
958
+ {
959
+ $ keyTypesCount = count ($ this ->keyTypes );
960
+ if ($ keyTypesCount === 0 ) {
961
+ return $ this ;
962
+ }
963
+
964
+ $ offset = $ offsetType instanceof ConstantIntegerType ? $ offsetType ->getValue () : 0 ;
965
+ $ length = $ lengthType instanceof ConstantIntegerType ? $ lengthType ->getValue () : $ keyTypesCount ;
966
+
967
+ if ($ length < 0 ) {
968
+ // Negative lengths prevent access to the most right n elements
969
+ return $ this ->removeLastElements ($ length * -1 )
970
+ ->sliceArray ($ offsetType , new NullType (), $ preserveKeys );
971
+ }
972
+
973
+ if ($ keyTypesCount + $ offset <= 0 ) {
974
+ // A negative offset cannot reach left outside the array
975
+ $ offset = 0 ;
976
+ }
977
+
978
+ if ($ offset < 0 ) {
979
+ /*
980
+ * Transforms the problem with the negative offset in one with a positive offset using array reversion.
981
+ * The reason is belows handling of optional keys which works only from left to right.
982
+ *
983
+ * e.g.
984
+ * array{a: 0, b: 1, c: 2, d: 3, e: 4}
985
+ * with offset -4 and length 2 (which would be sliced to array{b: 1, c: 2})
986
+ *
987
+ * is transformed via reversion to
988
+ *
989
+ * array{e: 4, d: 3, c: 2, b: 1, a: 0}
990
+ * with offset 2 and length 2 (which will be sliced to array{c: 2, b: 1} and then reversed again)
991
+ */
992
+ $ offset *= -1 ;
993
+ $ reversedLength = min ($ length , $ offset );
994
+ $ reversedOffset = $ offset - $ reversedLength ;
995
+ return $ this ->reverseArray (TrinaryLogic::createYes ())
996
+ ->sliceArray (new ConstantIntegerType ($ reversedOffset ), new ConstantIntegerType ($ reversedLength ), $ preserveKeys )
997
+ ->reverseArray (TrinaryLogic::createYes ());
998
+ }
999
+
1000
+ if ($ offset > 0 ) {
1001
+ return $ this ->removeFirstElements ($ offset , false )
1002
+ ->sliceArray (new ConstantIntegerType (0 ), $ lengthType , $ preserveKeys );
1003
+ }
1004
+
1005
+ $ builder = ConstantArrayTypeBuilder::createEmpty ();
1006
+
1007
+ $ nonOptionalElementsCount = 0 ;
1008
+ $ hasOptional = false ;
1009
+ for ($ i = 0 ; $ nonOptionalElementsCount < $ length && $ i < $ keyTypesCount ; $ i ++) {
1010
+ $ isOptional = $ this ->isOptionalKey ($ i );
1011
+ if (!$ isOptional ) {
1012
+ $ nonOptionalElementsCount ++;
1013
+ } else {
1014
+ $ hasOptional = true ;
1015
+ }
1016
+
1017
+ $ isLastElement = $ nonOptionalElementsCount >= $ length || $ i + 1 >= $ keyTypesCount ;
1018
+ if ($ isLastElement && $ length < $ keyTypesCount && $ hasOptional ) {
1019
+ // If the slice is not full yet, but has at least one optional key
1020
+ // the last non-optional element is going to be optional.
1021
+ // Otherwise, it would not fit into the slice if previous non-optional keys are there.
1022
+ $ isOptional = true ;
1023
+ }
1024
+
1025
+ $ builder ->setOffsetValueType ($ this ->keyTypes [$ i ], $ this ->valueTypes [$ i ], $ isOptional );
1026
+ }
1027
+
1028
+ $ slice = $ builder ->getArray ();
1029
+ if (!$ slice instanceof self) {
1030
+ throw new ShouldNotHappenException ();
1031
+ }
1032
+
1033
+ return $ preserveKeys ->yes () ? $ slice : $ slice ->reindex ();
1034
+ }
1035
+
960
1036
public function isIterableAtLeastOnce (): TrinaryLogic
961
1037
{
962
1038
$ keysCount = count ($ this ->keyTypes );
@@ -1147,87 +1223,30 @@ private function removeFirstElements(int $length, bool $reindex = true): self
1147
1223
return $ array ;
1148
1224
}
1149
1225
1226
+ /** @deprecated Use sliceArray() instead */
1150
1227
public function slice (int $ offset , ?int $ limit , bool $ preserveKeys = false ): self
1151
1228
{
1152
- $ keyTypesCount = count ($ this ->keyTypes );
1153
- if ($ keyTypesCount === 0 ) {
1154
- return $ this ;
1155
- }
1156
-
1157
- $ limit ??= $ keyTypesCount ;
1158
- if ($ limit < 0 ) {
1159
- // Negative limits prevent access to the most right n elements
1160
- return $ this ->removeLastElements ($ limit * -1 )
1161
- ->slice ($ offset , null , $ preserveKeys );
1162
- }
1163
-
1164
- if ($ keyTypesCount + $ offset <= 0 ) {
1165
- // A negative offset cannot reach left outside the array
1166
- $ offset = 0 ;
1167
- }
1168
-
1169
- if ($ offset < 0 ) {
1170
- /*
1171
- * Transforms the problem with the negative offset in one with a positive offset using array reversion.
1172
- * The reason is belows handling of optional keys which works only from left to right.
1173
- *
1174
- * e.g.
1175
- * array{a: 0, b: 1, c: 2, d: 3, e: 4}
1176
- * with offset -4 and limit 2 (which would be sliced to array{b: 1, c: 2})
1177
- *
1178
- * is transformed via reversion to
1179
- *
1180
- * array{e: 4, d: 3, c: 2, b: 1, a: 0}
1181
- * with offset 2 and limit 2 (which will be sliced to array{c: 2, b: 1} and then reversed again)
1182
- */
1183
- $ offset *= -1 ;
1184
- $ reversedLimit = min ($ limit , $ offset );
1185
- $ reversedOffset = $ offset - $ reversedLimit ;
1186
- return $ this ->reverseConstantArray (TrinaryLogic::createYes ())
1187
- ->slice ($ reversedOffset , $ reversedLimit , $ preserveKeys )
1188
- ->reverseConstantArray (TrinaryLogic::createYes ());
1189
- }
1190
-
1191
- if ($ offset > 0 ) {
1192
- return $ this ->removeFirstElements ($ offset , false )
1193
- ->slice (0 , $ limit , $ preserveKeys );
1194
- }
1195
-
1196
- $ builder = ConstantArrayTypeBuilder::createEmpty ();
1197
-
1198
- $ nonOptionalElementsCount = 0 ;
1199
- $ hasOptional = false ;
1200
- for ($ i = 0 ; $ nonOptionalElementsCount < $ limit && $ i < $ keyTypesCount ; $ i ++) {
1201
- $ isOptional = $ this ->isOptionalKey ($ i );
1202
- if (!$ isOptional ) {
1203
- $ nonOptionalElementsCount ++;
1204
- } else {
1205
- $ hasOptional = true ;
1206
- }
1207
-
1208
- $ isLastElement = $ nonOptionalElementsCount >= $ limit || $ i + 1 >= $ keyTypesCount ;
1209
- if ($ isLastElement && $ limit < $ keyTypesCount && $ hasOptional ) {
1210
- // If the slice is not full yet, but has at least one optional key
1211
- // the last non-optional element is going to be optional.
1212
- // Otherwise, it would not fit into the slice if previous non-optional keys are there.
1213
- $ isOptional = true ;
1214
- }
1215
-
1216
- $ builder ->setOffsetValueType ($ this ->keyTypes [$ i ], $ this ->valueTypes [$ i ], $ isOptional );
1217
- }
1218
-
1219
- $ slice = $ builder ->getArray ();
1220
- if (!$ slice instanceof self) {
1229
+ $ array = $ this ->sliceArray (
1230
+ ConstantTypeHelper::getTypeFromValue ($ offset ),
1231
+ ConstantTypeHelper::getTypeFromValue ($ limit ),
1232
+ TrinaryLogic::createFromBoolean ($ preserveKeys ),
1233
+ );
1234
+ if (!$ array instanceof self) {
1221
1235
throw new ShouldNotHappenException ();
1222
1236
}
1223
1237
1224
- return $ preserveKeys ? $ slice : $ slice -> reindex () ;
1238
+ return $ array ;
1225
1239
}
1226
1240
1227
1241
/** @deprecated Use reverseArray() instead */
1228
1242
public function reverse (bool $ preserveKeys = false ): self
1229
1243
{
1230
- return $ this ->reverseConstantArray (TrinaryLogic::createFromBoolean ($ preserveKeys ));
1244
+ $ array = $ this ->reverseArray (TrinaryLogic::createFromBoolean ($ preserveKeys ));
1245
+ if (!$ array instanceof self) {
1246
+ throw new ShouldNotHappenException ();
1247
+ }
1248
+
1249
+ return $ array ;
1231
1250
}
1232
1251
1233
1252
/**
0 commit comments