@@ -919,10 +919,7 @@ public function shiftArray(): Type
919919
920920 public function shuffleArray (): Type
921921 {
922- $ builder = ConstantArrayTypeBuilder::createFromConstantArray ($ this ->getValuesArray ());
923- $ builder ->degradeToGeneralArray ();
924-
925- return $ builder ->getArray ();
922+ return $ this ->getValuesArray ()->degradeToGeneralArray ();
926923 }
927924
928925 public function sliceArray (Type $ offsetType , Type $ lengthType , TrinaryLogic $ preserveKeys ): Type
@@ -943,10 +940,7 @@ public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $pre
943940 }
944941
945942 if ($ offset === null || $ length === null ) {
946- $ builder = ConstantArrayTypeBuilder::createFromConstantArray ($ this );
947- $ builder ->degradeToGeneralArray ();
948-
949- return $ builder ->getArray ()
943+ return $ this ->degradeToGeneralArray ()
950944 ->sliceArray ($ offsetType , $ lengthType , $ preserveKeys );
951945 }
952946
@@ -1028,6 +1022,108 @@ public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $pre
10281022 return $ builder ->getArray ();
10291023 }
10301024
1025+ public function spliceArray (Type $ offsetType , Type $ lengthType , Type $ replacementType ): Type
1026+ {
1027+ $ keyTypesCount = count ($ this ->keyTypes );
1028+
1029+ $ offset = $ offsetType instanceof ConstantIntegerType ? $ offsetType ->getValue () : null ;
1030+
1031+ if ($ lengthType instanceof ConstantIntegerType) {
1032+ $ length = $ lengthType ->getValue ();
1033+ } elseif ($ lengthType ->isNull ()->yes ()) {
1034+ $ length = $ keyTypesCount ;
1035+ } else {
1036+ $ length = null ;
1037+ }
1038+
1039+ if ($ offset === null || $ length === null ) {
1040+ return $ this ->degradeToGeneralArray ()
1041+ ->spliceArray ($ offsetType , $ lengthType , $ replacementType );
1042+ }
1043+
1044+ if ($ keyTypesCount + $ offset <= 0 ) {
1045+ // A negative offset cannot reach left outside the array twice
1046+ $ offset = 0 ;
1047+ }
1048+
1049+ if ($ keyTypesCount + $ length <= 0 ) {
1050+ // A negative length cannot reach left outside the array twice
1051+ $ length = 0 ;
1052+ }
1053+
1054+ $ offsetWasNegative = false ;
1055+ if ($ offset < 0 ) {
1056+ $ offsetWasNegative = true ;
1057+ $ offset = $ keyTypesCount + $ offset ;
1058+ }
1059+
1060+ if ($ length < 0 ) {
1061+ $ length = $ keyTypesCount - $ offset + $ length ;
1062+ }
1063+
1064+ $ extractType = $ this ->sliceArray ($ offsetType , $ lengthType , TrinaryLogic::createYes ());
1065+
1066+ $ types = [];
1067+ foreach ($ replacementType ->toArray ()->getArrays () as $ replacementArrayType ) {
1068+ $ removeKeysCount = 0 ;
1069+ $ optionalKeysBeforeReplacement = 0 ;
1070+
1071+ $ builder = ConstantArrayTypeBuilder::createEmpty ();
1072+ for ($ i = 0 ;; $ i ++) {
1073+ $ isOptional = $ this ->isOptionalKey ($ i );
1074+
1075+ if (!$ offsetWasNegative && $ i < $ offset && $ isOptional ) {
1076+ $ optionalKeysBeforeReplacement ++;
1077+ }
1078+
1079+ if ($ i === $ offset + $ optionalKeysBeforeReplacement ) {
1080+ // When the offset is reached we have to a) put the replacement array in and b) remove $length elements
1081+ $ removeKeysCount = $ length ;
1082+
1083+ if ($ replacementArrayType instanceof self) {
1084+ $ valuesArray = $ replacementArrayType ->getValuesArray ();
1085+ for ($ j = 0 , $ jMax = count ($ valuesArray ->keyTypes ); $ j < $ jMax ; $ j ++) {
1086+ $ builder ->setOffsetValueType (null , $ valuesArray ->valueTypes [$ j ], $ valuesArray ->isOptionalKey ($ j ));
1087+ }
1088+ } else {
1089+ $ builder ->degradeToGeneralArray ();
1090+ $ builder ->setOffsetValueType ($ replacementArrayType ->getValuesArray ()->getIterableKeyType (), $ replacementArrayType ->getIterableValueType (), true );
1091+ }
1092+ }
1093+
1094+ if (!isset ($ this ->keyTypes [$ i ])) {
1095+ break ;
1096+ }
1097+
1098+ if ($ removeKeysCount > 0 ) {
1099+ $ extractTypeHasOffsetValueType = $ extractType ->hasOffsetValueType ($ this ->keyTypes [$ i ]);
1100+
1101+ if (
1102+ (!$ isOptional && $ extractTypeHasOffsetValueType ->yes ())
1103+ || ($ isOptional && $ extractTypeHasOffsetValueType ->maybe ())
1104+ ) {
1105+ $ removeKeysCount --;
1106+ continue ;
1107+ }
1108+ }
1109+
1110+ if (!$ isOptional && $ extractType ->hasOffsetValueType ($ this ->keyTypes [$ i ])->maybe ()) {
1111+ $ isOptional = true ;
1112+ }
1113+
1114+ $ builder ->setOffsetValueType (
1115+ $ this ->keyTypes [$ i ]->isInteger ()->no () ? $ this ->keyTypes [$ i ] : null ,
1116+ $ this ->valueTypes [$ i ],
1117+ $ isOptional ,
1118+ );
1119+ }
1120+
1121+ $ types [] = $ builder ->getArray ();
1122+ }
1123+
1124+ return TypeCombinator::union (...$ types );
1125+ }
1126+
10311127 public function isIterableAtLeastOnce (): TrinaryLogic
10321128 {
10331129 $ keysCount = count ($ this ->keyTypes );
@@ -1268,6 +1364,14 @@ public function generalizeValues(): self
12681364 return new self ($ this ->keyTypes , $ valueTypes , $ this ->nextAutoIndexes , $ this ->optionalKeys , $ this ->isList );
12691365 }
12701366
1367+ private function degradeToGeneralArray (): Type
1368+ {
1369+ $ builder = ConstantArrayTypeBuilder::createFromConstantArray ($ this );
1370+ $ builder ->degradeToGeneralArray ();
1371+
1372+ return $ builder ->getArray ();
1373+ }
1374+
12711375 public function getKeysArray (): self
12721376 {
12731377 return $ this ->getKeysOrValuesArray ($ this ->keyTypes );
0 commit comments