diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TupleValueImpl.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TupleValueImpl.java index c13fb7c60c28..2bdc52b627bb 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TupleValueImpl.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TupleValueImpl.java @@ -736,6 +736,7 @@ protected void checkFixedLength(long length) { @Override protected void unshift(long index, Object[] vals) { handleImmutableArrayValue(); + validateInherentTypeOfExistingMembers((int) index, vals.length); unshiftArray(index, vals.length, getCurrentArrayLength()); addToRefArray(vals, (int) index); } @@ -804,7 +805,8 @@ private void addToRefArray(Object[] vals, int startIndex) { } private void unshiftArray(long index, int unshiftByN, int arrLength) { - int lastIndex = size() + unshiftByN - 1; + int currSize = size(); + int lastIndex = currSize + unshiftByN - 1; prepareForConsecutiveMultiAdd(lastIndex, arrLength); if (index > lastIndex) { throw ErrorHelper.getRuntimeException(getModulePrefixedReason(ARRAY_LANG_LIB, @@ -812,7 +814,7 @@ private void unshiftArray(long index, int unshiftByN, int arrLength) { } int i = (int) index; - System.arraycopy(this.refValues, i, this.refValues, i + unshiftByN, this.size - i); + System.arraycopy(this.refValues, i, this.refValues, i + unshiftByN, currSize - i); } private int getCurrentArrayLength() { @@ -842,4 +844,17 @@ private void validateTupleSizeAndInherentType() { } } } + + private void validateInherentTypeOfExistingMembers(int index, int offset) { + Type targetType; + for (int i = index; i < this.size; i++) { + targetType = (i + offset >= this.tupleType.getTupleTypes().size()) ? + this.tupleType.getRestType() : this.tupleType.getTupleTypes().get(i + offset); + if (!TypeChecker.checkIsType(this.getRefValue(i), targetType)) { + throw ErrorHelper.getRuntimeException(getModulePrefixedReason(ARRAY_LANG_LIB, + INHERENT_TYPE_VIOLATION_ERROR_IDENTIFIER), + ErrorCodes.INCOMPATIBLE_TYPE, TypeChecker.getType(this.getRefValue(i)), targetType); + } + } + } } diff --git a/langlib/lang.array/src/main/java/org/ballerinalang/langlib/array/Unshift.java b/langlib/lang.array/src/main/java/org/ballerinalang/langlib/array/Unshift.java index d4c1e1e21680..d02c83e31335 100644 --- a/langlib/lang.array/src/main/java/org/ballerinalang/langlib/array/Unshift.java +++ b/langlib/lang.array/src/main/java/org/ballerinalang/langlib/array/Unshift.java @@ -18,11 +18,8 @@ package org.ballerinalang.langlib.array; -import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BArray; -import static org.ballerinalang.langlib.array.utils.ArrayUtils.checkIsArrayOnlyOperation; - /** * Native implementation of lang.array:unshift((any|error)[], (any|error)...). * @@ -31,7 +28,6 @@ public class Unshift { public static void unshift(BArray arr, Object... vals) { - checkIsArrayOnlyOperation(TypeUtils.getImpliedType(arr.getType()), "unshift()"); arr.unshift(vals); } } diff --git a/langlib/langlib-test/src/test/java/org/ballerinalang/langlib/test/LangLibArrayTest.java b/langlib/langlib-test/src/test/java/org/ballerinalang/langlib/test/LangLibArrayTest.java index 8b7a128b0f10..f8dc94bbf0e0 100644 --- a/langlib/langlib-test/src/test/java/org/ballerinalang/langlib/test/LangLibArrayTest.java +++ b/langlib/langlib-test/src/test/java/org/ballerinalang/langlib/test/LangLibArrayTest.java @@ -552,6 +552,7 @@ public Object[] testFunctions() { "testLastIndexOf", "testPush", "testShiftOperation", + "testUnshiftOperation", "testSort1", "testSort2", "testSort4", diff --git a/langlib/langlib-test/src/test/resources/test-src/arraylib_test.bal b/langlib/langlib-test/src/test/resources/test-src/arraylib_test.bal index c4f68d97b579..3a7ceb07f9a2 100644 --- a/langlib/langlib-test/src/test/resources/test-src/arraylib_test.bal +++ b/langlib/langlib-test/src/test/resources/test-src/arraylib_test.bal @@ -803,6 +803,89 @@ function testShiftOnTupleWithProperConditions() { assertValueEquality([person2], properTuple5); } +type UnshiftType1 record {string val1; int val2;}; +type UnshiftType2 record {string val1;}; +function testUnshiftOperation() { + testUnshiftTuplesPositives(); + testUnshiftTuplesNegatives(); +} + +function testUnshiftTuplesPositives() { + [int, int...] tuple1 = []; + [int, int, int...] tuple3 = [1, 3, 4, 5, 7]; + [int, int...] list = []; + [UnshiftType1, UnshiftType1...] tuple4 = [{val1:"Hello", val2:67}]; + + tuple1.unshift(5, 6, 7, 8); + tuple3.unshift(12, 67); + foreach int i in 0 ..< 400 { + list.unshift(i); + } + tuple4.unshift({val1:"world", val2:82}); + + assertValueEquality([5, 6, 7, 8, 0], tuple1); + assertValueEquality([12, 67, 1, 3, 4, 5, 7], tuple3); + assertValueEquality(401, list.length()); + assertValueEquality(399, list[0]); + assertValueEquality(199, list[200]); + assertValueEquality(0, list[399]); + assertValueEquality(0, list[400]); + assertValueEquality([{val1:"world", val2:82}, {val1:"Hello", val2:67}], tuple4); +} + +function testUnshiftTuplesNegatives() { + [boolean, string...] tuple1 = []; + [int, boolean, string...] tuple2 = [8, false, "hello"]; + [int, boolean, string...] tuple3 = []; + [UnshiftType1, UnshiftType2...] tuple4 = [{val1:"Hello", val2:67}]; + + var fn1 = function() { + tuple1.unshift("hello"); + }; + + var fn2 = function() { + tuple2.unshift(false, 78, "world"); + }; + + var fn3 = function() { + tuple3.unshift(10, false, "Hello", "World"); + }; + + var fn4 = function() { + tuple4.unshift({val1:"world"}); + }; + + error? res1 = trap fn1(); + error? res2 = trap fn2(); + error? res3 = trap fn3(); + error? res4 = trap fn4(); + assertTrue(res1 is error); + assertTrue(res2 is error); + assertTrue(res3 is error); + assertTrue(res4 is error); + + error err1 = res1; + error err2 = res2; + error err3 = res3; + error err4 = res4; + var message1 = err1.detail()["message"]; + var message2 = err2.detail()["message"]; + var message3 = err3.detail()["message"]; + var message4 = err4.detail()["message"]; + string detailMessage1 = message1 is error ? message1.toString() : message1.toString(); + string detailMessage2 = message2 is error ? message2.toString() : message2.toString(); + string detailMessage3 = message3 is error ? message3.toString() : message3.toString(); + string detailMessage4 = message4 is error ? message4.toString() : message4.toString(); + assertValueEquality("{ballerina/lang.array}InherentTypeViolation", err1.message()); + assertValueEquality("incompatible types: expected 'boolean', found 'string'", detailMessage1); + assertValueEquality("{ballerina/lang.array}InherentTypeViolation", err2.message()); + assertValueEquality("incompatible types: expected 'int', found 'string'", detailMessage2); + assertValueEquality("{ballerina/lang.array}InherentTypeViolation", err3.message()); + assertValueEquality("incompatible types: expected 'int', found 'string'", detailMessage3); + assertValueEquality("{ballerina/lang.array}InherentTypeViolation", err4.message()); + assertValueEquality("incompatible types: expected 'UnshiftType1', found 'UnshiftType2'", detailMessage4); +} + type Student record {| int id; string? fname;