@@ -945,7 +945,7 @@ public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $pre
945
945
}
946
946
947
947
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
949
949
$ offset = 0 ;
950
950
}
951
951
@@ -1006,6 +1006,120 @@ public function sliceArray(Type $offsetType, Type $lengthType, TrinaryLogic $pre
1006
1006
return $ builder ->getArray ();
1007
1007
}
1008
1008
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
+
1009
1123
public function isIterableAtLeastOnce (): TrinaryLogic
1010
1124
{
1011
1125
$ keysCount = count ($ this ->keyTypes );
0 commit comments