@@ -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,120 @@ 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 ($ offset === null || $ length === null ) {
1024+ return $ this ->degradeToGeneralArray ()
1025+ ->spliceArray ($ offsetType , $ lengthType , $ replacementType );
1026+ }
1027+
1028+ if ($ keyTypesCount + $ offset <= 0 ) {
1029+ // A negative offset cannot reach left outside the array twice
1030+ $ offset = 0 ;
1031+ }
1032+
1033+ if ($ offset < 0 ) {
1034+ /*
1035+ * Transforms the problem with the negative offset in one with a positive offset using array reversion.
1036+ * The reason is belows handling of optional keys which works only from left to right.
1037+ *
1038+ * e.g.
1039+ * array{a: 0, b: 1, c: 2, d: 3, e: 4}
1040+ * with offset -4, length 2 and replacement array{17, 19} (which would be spliced to array{a: 0, 0: 17, 1: 19, d: 3, e: 4})
1041+ *
1042+ * is transformed via reversion to
1043+ *
1044+ * array{e: 4, d: 3, c: 2, b: 1, a: 0}
1045+ * with offset 2 and length 2 (which will be spliced to array{e: 4, d: 3, 1: 19, 0: 17, a: 0} and then reversed again)
1046+ */
1047+ $ offset *= -1 ;
1048+ $ reversedLength = min ($ length , $ offset );
1049+ $ reversedOffset = $ offset - $ reversedLength ;
1050+ return $ this ->reverseArray (TrinaryLogic::createNo ())
1051+ ->spliceArray (new ConstantIntegerType ($ reversedOffset ), new ConstantIntegerType ($ reversedLength ), $ replacementType ->reverseArray (TrinaryLogic::createYes ()))
1052+ ->reverseArray (TrinaryLogic::createNo ());
1053+ }
1054+
1055+ if ($ length < 0 ) {
1056+ $ length = $ keyTypesCount - $ offset - $ length * -1 ;
1057+ }
1058+
1059+ $ removeKeysCount = 0 ;
1060+ $ optionalKeysIgnored = 0 ;
1061+ $ optionalKeysBeforeReplacement = 0 ;
1062+ $ builder = ConstantArrayTypeBuilder::createEmpty ();
1063+ for ($ i = 0 ;; $ i ++) {
1064+ $ isOptional = $ this ->isOptionalKey ($ i );
1065+
1066+ if ($ i < $ offset && $ isOptional ) {
1067+ $ optionalKeysIgnored ++;
1068+ $ optionalKeysBeforeReplacement ++;
1069+ }
1070+
1071+ if ($ i === $ offset + $ optionalKeysBeforeReplacement ) {
1072+ // When the offset is reached we have to a) put the replacement array in and b) remove $length elements
1073+ $ removeKeysCount = $ length ;
1074+ $ optionalKeysIgnored += $ optionalKeysBeforeReplacement ;
1075+
1076+ $ replacementArrayType = $ replacementType ->toArray ();
1077+ $ constantArrays = $ replacementArrayType ->getConstantArrays ();
1078+ if (count ($ constantArrays ) === 1 ) {
1079+ $ valuesArray = $ constantArrays [0 ]->getValuesArray ();
1080+ for ($ j = 0 , $ jMax = count ($ valuesArray ->keyTypes ); $ j < $ jMax ; $ j ++) {
1081+ $ builder ->setOffsetValueType (null , $ valuesArray ->valueTypes [$ j ], $ valuesArray ->isOptionalKey ($ j ));
1082+ }
1083+ } else {
1084+ $ builder ->degradeToGeneralArray ();
1085+ $ builder ->setOffsetValueType (null , $ replacementArrayType ->getIterableValueType (), true );
1086+ }
1087+ }
1088+
1089+ if (!isset ($ this ->keyTypes [$ i ])) {
1090+ break ;
1091+ }
1092+
1093+ if ($ removeKeysCount > 0 ) {
1094+ $ removeKeysCount --;
1095+
1096+ if ($ optionalKeysIgnored === 0 ) {
1097+ if ($ isOptional ) {
1098+ $ optionalKeysIgnored ++;
1099+ }
1100+
1101+ continue ;
1102+ } else {
1103+ $ optionalKeysIgnored ++;
1104+ $ isOptional = true ;
1105+ }
1106+ }
1107+
1108+ if (!$ isOptional && $ optionalKeysIgnored > 0 ) {
1109+ $ optionalKeysIgnored --;
1110+ $ isOptional = true ;
1111+ }
1112+
1113+ $ builder ->setOffsetValueType (
1114+ $ this ->keyTypes [$ i ]->isInteger ()->no () ? $ this ->keyTypes [$ i ] : null ,
1115+ $ this ->valueTypes [$ i ],
1116+ $ isOptional ,
1117+ );
1118+ }
1119+
1120+ return $ builder ->getArray ();
1121+ }
1122+
10091123 public function isIterableAtLeastOnce (): TrinaryLogic
10101124 {
10111125 $ keysCount = count ($ this ->keyTypes );
0 commit comments