@@ -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,97 @@ 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+ $ allArrays = $ this ->getAllArrays ();
1012+ if (count ($ allArrays ) > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT ) {
1013+ return $ this ->degradeToGeneralArray ()
1014+ ->spliceArray ($ offsetType , $ lengthType , $ replacementType );
1015+ }
1016+
1017+ $ types = [];
1018+ foreach ($ this ->getAllArrays () as $ array ) {
1019+ $ types [] = $ array ->spliceArrayWithoutOptionalKeys ($ offsetType , $ lengthType , $ replacementType );
1020+ }
1021+
1022+ return TypeCombinator::union (...$ types );
1023+ }
1024+
1025+ private function spliceArrayWithoutOptionalKeys (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+ if ($ offset < 0 ) {
1055+ $ offset = $ keyTypesCount + $ offset ;
1056+ }
1057+
1058+ if ($ length < 0 ) {
1059+ $ length = $ keyTypesCount - $ offset + $ length ;
1060+ }
1061+
1062+ $ removeKeysCount = 0 ;
1063+ $ builder = ConstantArrayTypeBuilder::createEmpty ();
1064+ for ($ i = 0 ;; $ i ++) {
1065+ if ($ i === $ offset ) {
1066+ // When the offset is reached we have to a) put the replacement array in and b) remove $length elements
1067+ $ removeKeysCount = $ length ;
1068+
1069+ $ replacementArrayType = $ replacementType ->toArray ();
1070+ $ constantArrays = $ replacementArrayType ->getConstantArrays ();
1071+ if (count ($ constantArrays ) === 1 ) {
1072+ $ valuesArray = $ constantArrays [0 ]->getValuesArray ();
1073+ for ($ j = 0 , $ jMax = count ($ valuesArray ->keyTypes ); $ j < $ jMax ; $ j ++) {
1074+ $ builder ->setOffsetValueType (null , $ valuesArray ->valueTypes [$ j ], $ valuesArray ->isOptionalKey ($ j ));
1075+ }
1076+ } else {
1077+ $ builder ->degradeToGeneralArray ();
1078+ $ builder ->setOffsetValueType ($ replacementArrayType ->getValuesArray ()->getIterableKeyType (), $ replacementArrayType ->getIterableValueType (), true );
1079+ }
1080+ }
1081+
1082+ if (!isset ($ this ->keyTypes [$ i ])) {
1083+ break ;
1084+ }
1085+
1086+ if ($ removeKeysCount > 0 ) {
1087+ $ removeKeysCount --;
1088+ continue ;
1089+ }
1090+
1091+ $ builder ->setOffsetValueType (
1092+ $ this ->keyTypes [$ i ]->isInteger ()->no () ? $ this ->keyTypes [$ i ] : null ,
1093+ $ this ->valueTypes [$ i ],
1094+ );
1095+ }
1096+
1097+ return $ builder ->getArray ();
1098+ }
1099+
10091100 public function isIterableAtLeastOnce (): TrinaryLogic
10101101 {
10111102 $ keysCount = count ($ this ->keyTypes );
0 commit comments