@@ -1016,6 +1016,97 @@ public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $pre
10161016 return $ builder ->getArray ();
10171017 }
10181018
1019+ public function spliceArray (Type $ offsetType , Type $ lengthType , Type $ replacementType ): Type
1020+ {
1021+ $ allArrays = $ this ->getAllArrays ();
1022+ if (count ($ allArrays ) > InitializerExprTypeResolver::CALCULATE_SCALARS_LIMIT ) {
1023+ return $ this ->degradeToGeneralArray ()
1024+ ->spliceArray ($ offsetType , $ lengthType , $ replacementType );
1025+ }
1026+
1027+ $ types = [];
1028+ foreach ($ this ->getAllArrays () as $ array ) {
1029+ $ types [] = $ array ->spliceArrayWithoutOptionalKeys ($ offsetType , $ lengthType , $ replacementType );
1030+ }
1031+
1032+ return TypeCombinator::union (...$ types );
1033+ }
1034+
1035+ private function spliceArrayWithoutOptionalKeys (Type $ offsetType , Type $ lengthType , Type $ replacementType ): Type
1036+ {
1037+ $ keyTypesCount = count ($ this ->keyTypes );
1038+
1039+ $ offset = $ offsetType instanceof ConstantIntegerType ? $ offsetType ->getValue () : null ;
1040+
1041+ if ($ lengthType instanceof ConstantIntegerType) {
1042+ $ length = $ lengthType ->getValue ();
1043+ } elseif ($ lengthType ->isNull ()->yes ()) {
1044+ $ length = $ keyTypesCount ;
1045+ } else {
1046+ $ length = null ;
1047+ }
1048+
1049+ if ($ offset === null || $ length === null ) {
1050+ return $ this ->degradeToGeneralArray ()
1051+ ->spliceArray ($ offsetType , $ lengthType , $ replacementType );
1052+ }
1053+
1054+ if ($ keyTypesCount + $ offset <= 0 ) {
1055+ // A negative offset cannot reach left outside the array twice
1056+ $ offset = 0 ;
1057+ }
1058+
1059+ if ($ keyTypesCount + $ length <= 0 ) {
1060+ // A negative length cannot reach left outside the array twice
1061+ $ length = 0 ;
1062+ }
1063+
1064+ if ($ offset < 0 ) {
1065+ $ offset = $ keyTypesCount + $ offset ;
1066+ }
1067+
1068+ if ($ length < 0 ) {
1069+ $ length = $ keyTypesCount - $ offset + $ length ;
1070+ }
1071+
1072+ $ removeKeysCount = 0 ;
1073+ $ builder = ConstantArrayTypeBuilder::createEmpty ();
1074+ for ($ i = 0 ;; $ i ++) {
1075+ if ($ i === $ offset ) {
1076+ // When the offset is reached we have to a) put the replacement array in and b) remove $length elements
1077+ $ removeKeysCount = $ length ;
1078+
1079+ $ replacementArrayType = $ replacementType ->toArray ();
1080+ $ constantArrays = $ replacementArrayType ->getConstantArrays ();
1081+ if (count ($ constantArrays ) === 1 ) {
1082+ $ valuesArray = $ constantArrays [0 ]->getValuesArray ();
1083+ for ($ j = 0 , $ jMax = count ($ valuesArray ->keyTypes ); $ j < $ jMax ; $ j ++) {
1084+ $ builder ->setOffsetValueType (null , $ valuesArray ->valueTypes [$ j ], $ valuesArray ->isOptionalKey ($ j ));
1085+ }
1086+ } else {
1087+ $ builder ->degradeToGeneralArray ();
1088+ $ builder ->setOffsetValueType ($ replacementArrayType ->getValuesArray ()->getIterableKeyType (), $ replacementArrayType ->getIterableValueType (), true );
1089+ }
1090+ }
1091+
1092+ if (!isset ($ this ->keyTypes [$ i ])) {
1093+ break ;
1094+ }
1095+
1096+ if ($ removeKeysCount > 0 ) {
1097+ $ removeKeysCount --;
1098+ continue ;
1099+ }
1100+
1101+ $ builder ->setOffsetValueType (
1102+ $ this ->keyTypes [$ i ]->isInteger ()->no () ? $ this ->keyTypes [$ i ] : null ,
1103+ $ this ->valueTypes [$ i ],
1104+ );
1105+ }
1106+
1107+ return $ builder ->getArray ();
1108+ }
1109+
10191110 public function isIterableAtLeastOnce (): TrinaryLogic
10201111 {
10211112 $ keysCount = count ($ this ->keyTypes );
0 commit comments