Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add checks and logic to support sort operation for tuples at runtime #43290

Merged
merged 16 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bvm/ballerina-runtime/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
io.ballerina.lang.function, io.ballerina.lang.regexp, io.ballerina.lang.value, io.ballerina.lang.internal, io.ballerina.lang.array;
exports io.ballerina.runtime.internal.configurable to io.ballerina.lang.internal;
exports io.ballerina.runtime.internal.types to io.ballerina.lang.typedesc, io.ballerina.testerina.runtime,
org.ballerinalang.debugadapter.runtime, io.ballerina.lang.function, io.ballerina.lang.regexp, io.ballerina.testerina.core;
org.ballerinalang.debugadapter.runtime, io.ballerina.lang.function, io.ballerina.lang.regexp, io.ballerina.testerina.core, io.ballerina.lang.array;
exports io.ballerina.runtime.observability.metrics.noop;
exports io.ballerina.runtime.observability.tracer.noop;
exports io.ballerina.runtime.internal.regexp;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,28 @@

package org.ballerinalang.langlib.array;

import io.ballerina.runtime.api.TypeTags;
import io.ballerina.runtime.api.creators.ErrorCreator;
import io.ballerina.runtime.api.creators.TypeCreator;
import io.ballerina.runtime.api.creators.ValueCreator;
import io.ballerina.runtime.api.utils.TypeUtils;
import io.ballerina.runtime.api.types.Type;
import io.ballerina.runtime.api.utils.StringUtils;
import io.ballerina.runtime.api.values.BArray;
import io.ballerina.runtime.api.values.BError;
import io.ballerina.runtime.api.values.BFunctionPointer;
import io.ballerina.runtime.api.values.BMap;
import io.ballerina.runtime.api.values.BString;
import io.ballerina.runtime.internal.ValueComparisonUtils;
import io.ballerina.runtime.internal.scheduling.Scheduler;
import io.ballerina.runtime.internal.types.BTupleType;

import java.util.HashSet;
import java.util.Set;

import static io.ballerina.runtime.api.constants.RuntimeConstants.ARRAY_LANG_LIB;
import static io.ballerina.runtime.internal.errors.ErrorReasons.INVALID_TYPE_TO_SORT;
import static io.ballerina.runtime.internal.errors.ErrorReasons.getModulePrefixedReason;
import static org.ballerinalang.langlib.array.utils.ArrayUtils.checkIsArrayOnlyOperation;
import static org.ballerinalang.langlib.array.utils.SortUtils.isOrderedType;

/**
* Native implementation of lang.array:sort((any|error)[], direction, function).
Expand All @@ -46,12 +52,16 @@ private Sort() {
}

public static BArray sort(BArray arr, Object direction, Object func) {
checkIsArrayOnlyOperation(TypeUtils.getImpliedType(arr.getType()), "sort()");
BArray sortedArray;
Type arrType = arr.getType();
BFunctionPointer<Object, Object> function = (BFunctionPointer<Object, Object>) func;

// Check if the array type is an Ordered type, otherwise a key function is mandatory
if (!isOrderedType(arrType) && function == null) {
throw ErrorCreator.createError(getModulePrefixedReason(ARRAY_LANG_LIB, INVALID_TYPE_TO_SORT),
StringUtils.fromString("valid key function required"));
}
Object[][] sortArr = new Object[arr.size()][2];
Object[][] sortArrClone = new Object[arr.size()][2];

if (function != null) {
for (int i = 0; i < arr.size(); i++) {
sortArr[i][0] = function.call(new Object[]{Scheduler.getStrand(), arr.get(i), true});
Expand All @@ -62,15 +72,21 @@ public static BArray sort(BArray arr, Object direction, Object func) {
sortArr[i][0] = sortArr[i][1] = arr.get(i);
}
}

mergesort(sortArr, sortArrClone, 0, sortArr.length - 1, direction.toString());

BArray sortedArray = ValueCreator.createArrayValue(TypeCreator.createArrayType(arr.getElementType()));

if (arrType.getTag() == TypeTags.TUPLE_TAG) {
BTupleType tupleType = (BTupleType) arrType;
Set<Type> typeList = new HashSet<>(tupleType.getTupleTypes());
if (tupleType.getRestType() != null) {
typeList.add(tupleType.getRestType());
}
sortedArray = ValueCreator.createArrayValue(TypeCreator.createArrayType(
TypeCreator.createUnionType(typeList.stream().toList())));
} else {
sortedArray = ValueCreator.createArrayValue(TypeCreator.createArrayType(arr.getElementType()));
}
for (int k = 0; k < sortArr.length; k++) {
sortedArray.add(k, sortArr[k][1]);
}

return sortedArray;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright (c) 2024, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.ballerinalang.langlib.array.utils;

import io.ballerina.runtime.api.TypeTags;
import io.ballerina.runtime.api.types.Type;
import io.ballerina.runtime.api.types.UnionType;
import io.ballerina.runtime.api.utils.TypeUtils;
import io.ballerina.runtime.internal.TypeChecker;
import io.ballerina.runtime.internal.types.BArrayType;
import io.ballerina.runtime.internal.types.BFiniteType;
import io.ballerina.runtime.internal.types.BTupleType;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* A utility class containing methods needed for the sort operation on tuples and arrays.
*
* @since 2201.11.0
*/
public class SortUtils {
ravinperera00 marked this conversation as resolved.
Show resolved Hide resolved
ravinperera00 marked this conversation as resolved.
Show resolved Hide resolved

/**
* A private constructor to avoid code coverage warnings.
*/
private SortUtils() {};

/**
* Check if the provided type is an Ordered type.
* @param type type to be checked.
* @return true if type is Ordered, false otherwise.
*/
public static boolean isOrderedType(Type type) {
type = TypeUtils.getImpliedType(type);
switch (type.getTag()) {
case TypeTags.UNION_TAG:
UnionType unionType = (UnionType) type;
if (unionType.isCyclic()) {
return true;
}
Set<Type> memberTypes = new HashSet<>(unionType.getMemberTypes());
Type firstTypeInUnion = TypeUtils.getImpliedType(memberTypes.stream().findFirst().
orElse(memberTypes.iterator().next()));
if (firstTypeInUnion.getTag() == TypeTags.NULL_TAG) {
// Union contains only the nil type.
return true;
}
if (!isOrderedType(firstTypeInUnion)) {
return false;
}
for (Type memType : memberTypes) {
memType = TypeUtils.getImpliedType(memType);
if (!isOrderedType(memType) || isDifferentOrderedType(memType, firstTypeInUnion)) {
return false;
}
}
return true;
case TypeTags.ARRAY_TAG:
return isOrderedType(((BArrayType) type).getElementType());
case TypeTags.TUPLE_TAG:
BTupleType tupleType = (BTupleType) type;
List<Type> tupleTypes = tupleType.getTupleTypes();
if (tupleType.getRestType() != null) {
tupleTypes.add(tupleType.getRestType());
}
if (!isOrderedType(tupleTypes.get(0))) {
return false;
}
for (Type memType : tupleTypes) {
if (!isOrderedType(memType) || isDifferentOrderedType(memType, tupleTypes.get(0))) {
return false;
}
}
return true;
case TypeTags.FINITE_TYPE_TAG:
BFiniteType finiteType = (BFiniteType) type;
Set<Object> valSpace = finiteType.getValueSpace();
Type baseExprType = TypeUtils.getType(valSpace.iterator().next());
if (!checkValueSpaceHasSameType(finiteType, baseExprType)) {
return false;
}
return isOrderedType(baseExprType);
default:
return isSimpleBasicType(type.getTag());
}
}

/**
* Check if the value space of the provided finite type belongs to the value space of the given type.
* @param finiteType finite type to be checked.
* @param type type to be checked against.
* @return true if the finite type belongs to the same value space, false otherwise.
*/
public static boolean checkValueSpaceHasSameType(BFiniteType finiteType, Type type) {
Type baseType = TypeUtils.getImpliedType(type);
if (baseType.getTag() == TypeTags.FINITE_TYPE_TAG) {
return checkValueSpaceHasSameType((BFiniteType) baseType,
TypeUtils.getType(finiteType.getValueSpace().iterator().next()));

Check warning on line 116 in langlib/lang.array/src/main/java/org/ballerinalang/langlib/array/utils/SortUtils.java

View check run for this annotation

Codecov / codecov/patch

langlib/lang.array/src/main/java/org/ballerinalang/langlib/array/utils/SortUtils.java#L115-L116

Added lines #L115 - L116 were not covered by tests
}
for (Object expr : finiteType.getValueSpace()) {
if (isDifferentOrderedType(TypeUtils.getType(expr), baseType)) {
return false;
}
}
return true;
}

/**
* Check whether a given type is different to a target type.
* @param source type to check.
* @param target type to compare with.
* @return true if the source type does not belong to the target type, false otherwise.
*/
public static boolean isDifferentOrderedType(Type source, Type target) {
source = TypeUtils.getImpliedType(source);
target = TypeUtils.getImpliedType(target);
if (source.getTag() == TypeTags.NULL_TAG || target.getTag() == TypeTags.NULL_TAG) {
return false;
}
return !TypeChecker.checkIsType(source, target);
SasinduDilshara marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Check whether the given type tag belongs to a simple basic type.
* @param tag type tag to check.
* @return true if the tag belongs to a simple basic type, false otherwise.
*/
public static boolean isSimpleBasicType(int tag) {
return switch (tag) {
case TypeTags.BYTE_TAG, TypeTags.FLOAT_TAG, TypeTags.DECIMAL_TAG, TypeTags.BOOLEAN_TAG, TypeTags.NULL_TAG ->
true;
default -> tag >= TypeTags.INT_TAG && tag <= TypeTags.CHAR_STRING_TAG;
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,8 @@ public Object[] testFunctions() {
"testSort8",
"testSort9",
"testSort10",
"testSort11",
"testSortNegative",
"testReadOnlyArrayFilter",
"testTupleFilter",
"testTupleReverse",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ public void testRemove() {
BRunUtil.invoke(compileResult, "testRemove");
}

@Test(expectedExceptions = RuntimeException.class,
expectedExceptionsMessageRegExp = ".*error: \\{ballerina/lang.array\\}OperationNotSupported.*")
@Test
public void testSort() {
BRunUtil.invoke(compileResult, "testSort");
}
Expand Down
Loading