@@ -945,7 +945,7 @@ public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $pre
945945 }
946946
947947 if ($ keyTypesCount + $ offset <= 0 ) {
948- // A negative offset cannot reach left outside the array
948+ // A negative offset cannot reach left outside the array twice
949949 $ offset = 0 ;
950950 }
951951
@@ -1006,6 +1006,108 @@ public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $pre
10061006 return $ builder ->getArray ();
10071007 }
10081008
1009+ public function spliceArray (Type $ offsetType , Type $ lengthType , Type $ replacementType ): Type
1010+ {
1011+ $ keyTypesCount = count ($ this ->keyTypes );
1012+
1013+ $ offset = $ offsetType instanceof ConstantIntegerType ? $ offsetType ->getValue () : null ;
1014+
1015+ if ($ lengthType instanceof ConstantIntegerType) {
1016+ $ length = $ lengthType ->getValue ();
1017+ } elseif ($ lengthType ->isNull ()->yes ()) {
1018+ $ length = $ keyTypesCount ;
1019+ } else {
1020+ $ length = null ;
1021+ }
1022+
1023+ if (
1024+ $ offset === null || $ length === null
1025+ || count ($ this ->optionalKeys ) > 0 && $ length < 0 // Negative lengths with optional keys are not supported yet
1026+ ) {
1027+ return $ this ->degradeToGeneralArray ()
1028+ ->spliceArray ($ offsetType , $ lengthType , $ replacementType );
1029+ }
1030+
1031+ if ($ keyTypesCount + $ offset <= 0 ) {
1032+ // A negative offset cannot reach left outside the array twice
1033+ $ offset = 0 ;
1034+ }
1035+
1036+ if ($ keyTypesCount + $ length <= 0 ) {
1037+ // A negative length cannot reach left outside the array twice
1038+ $ length = 0 ;
1039+ }
1040+
1041+ $ offsetWasNegative = false ;
1042+ if ($ offset < 0 ) {
1043+ $ offsetWasNegative = true ;
1044+ $ offset = $ keyTypesCount + $ offset ;
1045+ }
1046+
1047+ if ($ length < 0 ) {
1048+ $ length = $ keyTypesCount - $ offset + $ length ;
1049+ }
1050+
1051+ $ removeKeysCount = 0 ;
1052+ $ optionalKeysIgnored = 0 ;
1053+ $ optionalKeysBeforeReplacement = 0 ;
1054+ $ builder = ConstantArrayTypeBuilder::createEmpty ();
1055+ for ($ i = 0 ;; $ i ++) {
1056+ $ isOptional = $ this ->isOptionalKey ($ i );
1057+
1058+ if (!$ offsetWasNegative && $ i < $ offset && $ isOptional ) {
1059+ $ optionalKeysIgnored ++;
1060+ $ optionalKeysBeforeReplacement ++;
1061+ }
1062+
1063+ if ($ i === $ offset + $ optionalKeysBeforeReplacement ) {
1064+ // When the offset is reached we have to a) put the replacement array in and b) remove $length elements
1065+ $ removeKeysCount = $ length ;
1066+ $ optionalKeysIgnored += $ optionalKeysBeforeReplacement ;
1067+
1068+ $ replacementArrayType = $ replacementType ->toArray ();
1069+ $ constantArrays = $ replacementArrayType ->getConstantArrays ();
1070+ if (count ($ constantArrays ) === 1 ) {
1071+ $ valuesArray = $ constantArrays [0 ]->getValuesArray ();
1072+ for ($ j = 0 , $ jMax = count ($ valuesArray ->keyTypes ); $ j < $ jMax ; $ j ++) {
1073+ $ builder ->setOffsetValueType (null , $ valuesArray ->valueTypes [$ j ], $ valuesArray ->isOptionalKey ($ j ));
1074+ }
1075+ } else {
1076+ $ builder ->degradeToGeneralArray ();
1077+ $ builder ->setOffsetValueType (null , $ replacementArrayType ->getIterableValueType (), true );
1078+ }
1079+ }
1080+
1081+ if (!isset ($ this ->keyTypes [$ i ])) {
1082+ break ;
1083+ }
1084+
1085+ if ($ removeKeysCount > 0 ) {
1086+ $ removeKeysCount --;
1087+
1088+ if ($ optionalKeysIgnored === 0 ) {
1089+ if ($ isOptional && $ removeKeysCount === 0 ) {
1090+ $ optionalKeysIgnored ++;
1091+ }
1092+ continue ;
1093+ }
1094+ }
1095+
1096+ if (!$ isOptional && $ optionalKeysIgnored > 0 ) {
1097+ $ optionalKeysIgnored --;
1098+ $ isOptional = true ;
1099+ }
1100+
1101+ $ builder ->setOffsetValueType (
1102+ $ this ->keyTypes [$ i ]->isInteger ()->no () ? $ this ->keyTypes [$ i ] : null ,
1103+ $ this ->valueTypes [$ i ],
1104+ $ isOptional ,
1105+ );
1106+ }
1107+
1108+ return $ builder ->getArray ();
1109+ }
1110+
10091111 public function isIterableAtLeastOnce (): TrinaryLogic
10101112 {
10111113 $ keysCount = count ($ this ->keyTypes );
0 commit comments