3131use PHPStan \Type \CompoundType ;
3232use PHPStan \Type \ConstantScalarType ;
3333use PHPStan \Type \ConstantType ;
34+ use PHPStan \Type \ConstantTypeHelper ;
3435use PHPStan \Type \ErrorType ;
3536use PHPStan \Type \GeneralizePrecision ;
3637use PHPStan \Type \Generic \TemplateTypeMap ;
3940use PHPStan \Type \IntersectionType ;
4041use PHPStan \Type \MixedType ;
4142use PHPStan \Type \NeverType ;
43+ use PHPStan \Type \NullType ;
4244use PHPStan \Type \Type ;
4345use PHPStan \Type \TypeCombinator ;
4446use PHPStan \Type \UnionType ;
@@ -812,7 +814,7 @@ public function chunkArray(Type $lengthType, TrinaryLogic $preserveKeys): Type
812814
813815 $ keyTypesCount = count ($ this ->keyTypes );
814816 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 () );
816818 $ builder ->setOffsetValueType (null , $ preserveKeys ->yes () ? $ chunk : $ chunk ->getValuesArray ());
817819 }
818820
@@ -882,7 +884,7 @@ public function popArray(): Type
882884 return $ this ->removeLastElements (1 );
883885 }
884886
885- private function reverseConstantArray (TrinaryLogic $ preserveKeys ): self
887+ public function reverseArray (TrinaryLogic $ preserveKeys ): Type
886888 {
887889 $ keyTypesReversed = array_reverse ($ this ->keyTypes , true );
888890 $ keyTypes = array_values ($ keyTypesReversed );
@@ -894,11 +896,6 @@ private function reverseConstantArray(TrinaryLogic $preserveKeys): self
894896 return $ preserveKeys ->yes () ? $ reversed : $ reversed ->reindex ();
895897 }
896898
897- public function reverseArray (TrinaryLogic $ preserveKeys ): Type
898- {
899- return $ this ->reverseConstantArray ($ preserveKeys );
900- }
901-
902899 public function searchArray (Type $ needleType ): Type
903900 {
904901 $ matches = [];
@@ -957,6 +954,85 @@ public function shuffleArray(): Type
957954 return $ generalizedArray ;
958955 }
959956
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+
9601036 public function isIterableAtLeastOnce (): TrinaryLogic
9611037 {
9621038 $ keysCount = count ($ this ->keyTypes );
@@ -1147,87 +1223,30 @@ private function removeFirstElements(int $length, bool $reindex = true): self
11471223 return $ array ;
11481224 }
11491225
1226+ /** @deprecated Use sliceArray() instead */
11501227 public function slice (int $ offset , ?int $ limit , bool $ preserveKeys = false ): self
11511228 {
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) {
12211235 throw new ShouldNotHappenException ();
12221236 }
12231237
1224- return $ preserveKeys ? $ slice : $ slice -> reindex () ;
1238+ return $ array ;
12251239 }
12261240
12271241 /** @deprecated Use reverseArray() instead */
12281242 public function reverse (bool $ preserveKeys = false ): self
12291243 {
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 ;
12311250 }
12321251
12331252 /**
0 commit comments