diff --git a/CHANGELOG.md b/CHANGELOG.md index d2155fd52adf..eb74f366120f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -684,6 +684,7 @@ - [Ensure calls involving warnings remain instrumented][6067] - [One can define lazy atom fields][6151] - [Replace IOContexts with Execution Environment and generic Context][6171] +- [Vector.sort handles incomparable types][5998] [3227]: https://github.com/enso-org/enso/pull/3227 [3248]: https://github.com/enso-org/enso/pull/3248 @@ -789,6 +790,7 @@ [6067]: https://github.com/enso-org/enso/pull/6067 [6151]: https://github.com/enso-org/enso/pull/6151 [6171]: https://github.com/enso-org/enso/pull/6171 +[5998]: https://github.com/enso-org/enso/pull/5998 # Enso 2.0.0-alpha.18 (2021-10-12) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Any.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Any.enso index 6a2984689c05..1c41b6337ab6 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Any.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Any.enso @@ -481,11 +481,11 @@ type Any ## PRIVATE Checks if the comparators for the given objects are both of the same type. If so, - proceeds with the given action, and if not, throws `Type_Error`. -assert_same_comparators : Any -> Any -> (Any -> Any) -> Any ! Type_Error + proceeds with the given action, and if not, throws `Incomparable_Values` error. +assert_same_comparators : Any -> Any -> (Any -> Any) -> Any ! Incomparable_Values assert_same_comparators this that ~action = comp_this = Comparable.from this comp_that = Comparable.from that case Meta.is_same_object comp_this comp_that of True -> action - False -> Error.throw (Type_Error.Error (Meta.type_of comp_this) comp_that "comp_that") + False -> Error.throw (Incomparable_Values.Error this that) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Array.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Array.enso index 93968dc473fe..e5d742c9d493 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Array.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Array.enso @@ -14,6 +14,7 @@ import project.Error.Error import project.Errors.Common.Incomparable_Values import project.Errors.Common.Not_Found import project.Errors.Common.Index_Out_Of_Bounds +import project.Errors.Problem_Behavior.Problem_Behavior import project.Meta import project.Nothing.Nothing import project.Panic.Panic @@ -160,13 +161,17 @@ type Array Arguments: - order: The order in which the array elements are sorted. - on: A projection from the element type to the value of that element - being sorted on. + being sorted on. If set to `Nothing` (the default), + identity function will be used. - by: A function that compares the result of applying `on` to two - elements, returning an Ordering to compare them. + elements, returning an an `Ordering` if the two elements are comparable + or `Nothing` if they are not. If set to `Nothing` (the default argument), + `Ordering.compare _ _` method will be used. + - on_incomparable: A `Problem_Behavior` specifying what should happen if + two incomparable values are encountered. + + By default, elements are sorted in ascending order. - By default, elements are sorted in ascending order, using the comparator - acquired from each element. A custom compare function may be passed to - the sort method. This is a stable sort, meaning that items that compare the same will not have their order changed by the sorting process. @@ -178,12 +183,32 @@ type Array - *Average Time:* `O(n * log n)` - *Worst-Case Space:* `O(n)` additional + ? Incomparable values + Incomparable values are either values with different comparators or with + the same comparator returning `Nothing` from its `compare` method. + See the documentation of the `Ordering` module for more info. + ? Implementation Note The sort implementation is based upon an adaptive, iterative mergesort that requires far fewer than `n * log(n)` comparisons when the array is partially sorted. When the array is randomly ordered, the performance is equivalent to a standard mergesort. + ? Multiple comparators + Elements with different comparators are incomparable by definition. + This case is handled by first grouping the `self` array into groups + with the same comparator, recursively sorting these groups, and then + merging them back together. The order of the sorted groups in the + resulting array is based on the order of fully qualified names of + the comparators in the `self` array, with the exception of the group + for the default comparator, which is always the first group. + + Additionally, an `Incomparable_Values` dataflow error will be returned + if the `on_incomparable` parameter is set to `Problem_Behavior.Report_Error`, + or a warning attached if the `on_incomparable` parameter is set to + `Problem_Behavior.Report_Warning` in case of encountering incomparable + values. + It takes equal advantage of ascending and descending runs in the array, making it much simpler to merge two or more sorted arrays: simply concatenate them and sort. @@ -197,8 +222,16 @@ type Array Sorting an array of `Pair`s on the first element, descending. [Pair 1 2, Pair -1 8].to_array.sort Sort_Direction.Descending (_.first) - sort : Sort_Direction -> (Any -> Any) -> (Any -> Any -> Ordering) -> Vector Any ! Incomparable_Values - sort self (order = Sort_Direction.Ascending) (on = x -> x) (by = (Ordering.compare _ _)) = Vector.sort self order on by + + > Example + Sorting an array with elements with different comparators. Values `1` + and `My_Type` have different comparators. `1` will be sorted before `My_Type` + because it has the default comparator. + + [My_Type.Value 'hello', 1].to_array.sort == [1, My_Type.Value 'hello'].to_array + sort : Sort_Direction -> (Any -> Any)|Nothing -> (Any -> Any -> (Ordering|Nothing))|Nothing -> Problem_Behavior -> Vector Any ! Incomparable_Values + sort self (order = Sort_Direction.Ascending) on=Nothing by=Nothing on_incomparable=Problem_Behavior.Ignore = + Vector.sort self order on by on_incomparable ## Creates a new `Vector` with only the specified range of elements from the input, removing any elements outside the range. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Index_Sub_Range.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Index_Sub_Range.enso index 4ee181879da9..9f70c32dbec7 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Index_Sub_Range.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Index_Sub_Range.enso @@ -117,7 +117,7 @@ invert_range_selection ranges length needs_sorting = Empty subranges are discarded. sort_and_merge_ranges : Vector Range -> Vector Range sort_and_merge_ranges ranges = - sorted = ranges.filter (range-> range.is_empty.not) . sort on=(.start) + sorted = ranges.filter (range-> range.is_empty.not) . sort on=(_.start) if sorted.is_empty then [] else current_ref = Ref.new sorted.first builder = Vector.new_builder diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Numbers.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Numbers.enso index fa6c46b4ddf9..60cb49f039ca 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Numbers.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Numbers.enso @@ -1,6 +1,7 @@ import project.Data.Text.Text import project.Data.Locale.Locale import project.Errors.Common.Arithmetic_Error +import project.Errors.Common.Incomparable_Values import project.Error.Error import project.Nothing.Nothing import project.Panic.Panic @@ -279,6 +280,9 @@ type Number ## Checks equality of numbers, using an `epsilon` value. + ! Error Conditions + If either of the arguments is `Number.nan`, an `Incomparable_Values` error is raised. + Arguments: - that: The number to check equality against. - epsilon: The value by which `self` and `that` can be separated by before @@ -288,7 +292,7 @@ type Number Check if 1 is equal to 1.0000001 within 0.001. 1.equals 1.0000001 epsilon=0.001 - equals : Number -> Number -> Boolean + equals : Number -> Number -> Boolean ! Incomparable_Values equals self that epsilon=0.0 = (self == that) || ((self - that).abs <= epsilon) diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Ordering.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Ordering.enso index eb71ee6e5c4d..d293c85e3084 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Ordering.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Ordering.enso @@ -170,7 +170,7 @@ type Ordering Greater ## Compares to values and returns an Ordering - compare : Any -> Any -> Ordering ! (Incomparable_Values | Type_Error) + compare : Any -> Any -> Ordering ! Incomparable_Values compare x y = if x < y then Ordering.Less else if x == y then Ordering.Equal else diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Vector.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Vector.enso index c171b5db4f28..cbc488eb4c4a 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Vector.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Data/Vector.enso @@ -4,7 +4,6 @@ import project.Data.Filter_Condition.Filter_Condition import project.Data.List.List import project.Data.Map.Map import project.Data.Numbers.Integer -import project.Data.Ordering.Ordering import project.Data.Ordering.Sort_Direction.Sort_Direction import project.Data.Pair.Pair import project.Data.Range.Range @@ -17,12 +16,20 @@ import project.Errors.Common.Not_Found import project.Errors.Common.Type_Error import project.Error.Error import project.Errors.Illegal_Argument.Illegal_Argument +import project.Errors.Problem_Behavior.Problem_Behavior import project.Function.Function import project.Math +import project.Meta import project.Nothing.Nothing import project.Panic.Panic import project.Random +import project.Warning.Warning +import project.IO + +## We have to import also conversion methods, therefore, we import all from the Ordering + module +from project.Data.Ordering import all from project.Data.Boolean import Boolean, True, False from project.Data.Index_Sub_Range import Index_Sub_Range, take_helper, drop_helper @@ -841,13 +848,17 @@ type Vector a Arguments: - order: The order in which the vector elements are sorted. - on: A projection from the element type to the value of that element - being sorted on. + being sorted on. If set to `Nothing` (the default), + identity function will be used. - by: A function that compares the result of applying `on` to two - elements, returning an Ordering to compare them. + elements, returning an an `Ordering` if the two elements are comparable + or `Nothing` if they are not. If set to `Nothing` (the default argument), + `Ordering.compare _ _` method will be used. + - on_incomparable: A `Problem_Behavior` specifying what should happen if + two incomparable values are encountered. + + By default, elements are sorted in ascending order. - By default, elements are sorted in ascending order, using the comparator - acquired from each element. A custom compare function may be passed to - the sort method. This is a stable sort, meaning that items that compare the same will not have their order changed by the sorting process. @@ -859,12 +870,32 @@ type Vector a - *Average Time:* `O(n * log n)` - *Worst-Case Space:* `O(n)` additional + ? Incomparable values + Incomparable values are either values with different comparators or with + the same comparator returning `Nothing` from its `compare` method. + See the documentation of the `Ordering` module for more info. + ? Implementation Note The sort implementation is based upon an adaptive, iterative mergesort that requires far fewer than `n * log(n)` comparisons when the vector is partially sorted. When the vector is randomly ordered, the performance is equivalent to a standard mergesort. + ? Multiple comparators + Elements with different comparators are incomparable by definition. + This case is handled by first grouping the `self` vector into groups + with the same comparator, recursively sorting these groups, and then + merging them back together. The order of the sorted groups in the + resulting vector is based on the order of fully qualified names of + the comparators in the `self` vector, with the exception of the group + for the default comparator, which is always the first group. + + Additionally, an `Incomparable_Values` dataflow error will be returned + if the `on_incomparable` parameter is set to `Problem_Behavior.Report_Error`, + or a warning attached if the `on_incomparable` parameter is set to + `Problem_Behavior.Report_Warning` in case of encountering incomparable + values. + It takes equal advantage of ascending and descending runs in the array, making it much simpler to merge two or more sorted arrays: simply concatenate them and sort. @@ -878,16 +909,20 @@ type Vector a Sorting a vector of `Pair`s on the first element, descending. [Pair 1 2, Pair -1 8].sort Sort_Direction.Descending (_.first) - sort : Sort_Direction -> (Any -> Any) -> (Any -> Any -> Ordering) -> Vector Any ! Incomparable_Values - sort self (order = Sort_Direction.Ascending) (on = x -> x) (by = (Ordering.compare _ _)) = - comp_ascending l r = by (on l) (on r) - comp_descending l r = by (on r) (on l) - compare = if order == Sort_Direction.Ascending then comp_ascending else - comp_descending - - new_vec_arr = self.to_array.sort_builtin compare - if new_vec_arr.is_error then Error.throw new_vec_arr else - Vector.from_polyglot_array new_vec_arr + + > Example + Sorting a vector with elements with different comparators. Values `1` + and `My_Type` have different comparators. `1` will be sorted before `My_Type` + because it has the default comparator. + + [My_Type.Value 'hello', 1].sort == [1, My_Type.Value 'hello'] + sort : Sort_Direction -> (Any -> Any)|Nothing -> (Any -> Any -> (Ordering|Nothing))|Nothing -> Problem_Behavior -> Vector Any ! Incomparable_Values + sort self (order = Sort_Direction.Ascending) on=Nothing by=Nothing on_incomparable=Problem_Behavior.Ignore = + comps = case on == Nothing of + True -> self.map it-> Comparable.from it + False -> self.map it-> Comparable.from (on it) + compare_funcs = comps.map (it-> it.compare) + self.sort_builtin order.to_sign comps compare_funcs by on on_incomparable.to_number ## UNSTABLE Keeps only unique elements within the vector, removing any duplicates. diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Problem_Behavior.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Problem_Behavior.enso index 2b05336ffe73..969d8a78010b 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Problem_Behavior.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Errors/Problem_Behavior.enso @@ -164,3 +164,11 @@ type Problem_Behavior warnings = Warning.get_all result . map .value cleared_result = Warning.set result [] self.attach_problems_after cleared_result warnings + + ## PRIVATE + Returns a mapping of Problem_Behavior constructors to an integer. + Used for sending the number to Java, rather than sending the atom. + to_number self = case self of + Ignore -> 0 + Report_Warning -> 1 + Report_Error -> 2 diff --git a/distribution/lib/Standard/Test/0.0.0-dev/src/Problems.enso b/distribution/lib/Standard/Test/0.0.0-dev/src/Problems.enso index c5d90fa81b43..7167a2a144ac 100644 --- a/distribution/lib/Standard/Test/0.0.0-dev/src/Problems.enso +++ b/distribution/lib/Standard/Test/0.0.0-dev/src/Problems.enso @@ -3,7 +3,7 @@ from Standard.Base import all from project import Test import project.Extensions -## Returns values of warnings attached to the value.Nothing +## Returns values of warnings attached to the value. get_attached_warnings v = Warning.get_all v . map .value diff --git a/engine/polyglot-api/src/main/java/org/enso/polyglot/HostEnsoUtils.java b/engine/polyglot-api/src/main/java/org/enso/polyglot/HostEnsoUtils.java index 6e1fe5439133..7c782dc4802f 100644 --- a/engine/polyglot-api/src/main/java/org/enso/polyglot/HostEnsoUtils.java +++ b/engine/polyglot-api/src/main/java/org/enso/polyglot/HostEnsoUtils.java @@ -9,7 +9,7 @@ private HostEnsoUtils() { /** * Extracts a string representation for a polyglot exception. * - * @param exexception the exception + * @param ex the exception * @return message representing the exception */ public static String findExceptionMessage(Throwable ex) { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNode.java index fb5ab323a0da..173a4afe9af4 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNode.java @@ -2,6 +2,7 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.UnsupportedMessageException; @@ -26,6 +27,7 @@ name = "type_of", description = "Returns the type of a value.", autoRegister = false) +@GenerateUncached public abstract class TypeOfNode extends Node { public abstract Object execute(@AcceptsError Object value); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SortNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SortNode.java deleted file mode 100644 index ff825b69f04a..000000000000 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/mutable/SortNode.java +++ /dev/null @@ -1,160 +0,0 @@ -package org.enso.interpreter.node.expression.builtin.mutable; - -import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; -import com.oracle.truffle.api.dsl.Cached; -import com.oracle.truffle.api.dsl.Fallback; -import com.oracle.truffle.api.dsl.Specialization; -import com.oracle.truffle.api.interop.InteropLibrary; -import com.oracle.truffle.api.interop.InvalidArrayIndexException; -import com.oracle.truffle.api.interop.UnsupportedMessageException; -import com.oracle.truffle.api.library.CachedLibrary; -import com.oracle.truffle.api.nodes.LoopNode; -import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.profiles.BranchProfile; - -import java.util.Arrays; -import java.util.Comparator; - -import org.enso.interpreter.dsl.BuiltinMethod; -import org.enso.interpreter.node.callable.dispatch.CallOptimiserNode; -import org.enso.interpreter.node.callable.dispatch.SimpleCallOptimiserNode; -import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode; -import org.enso.interpreter.runtime.EnsoContext; -import org.enso.interpreter.runtime.callable.atom.Atom; -import org.enso.interpreter.runtime.callable.function.Function; -import org.enso.interpreter.runtime.data.Array; -import org.enso.interpreter.runtime.error.DataflowError; -import org.enso.interpreter.runtime.error.PanicException; -import org.enso.interpreter.runtime.state.State; - -@BuiltinMethod(type = "Array", name = "sort_builtin", description = "Returns a sorted array.") -public abstract class SortNode extends Node { - private @Child CallOptimiserNode callOptimiserNode = SimpleCallOptimiserNode.build(); - private @Child InvalidComparisonNode invalidComparisonNode = InvalidComparisonNode.build(); - private final BranchProfile invalidCompareResultProfile = BranchProfile.create(); - - abstract Object execute(State state, Object self, Object comparator); - - static SortNode build() { - return SortNodeGen.create(); - } - - @Specialization - Object doArray(State state, Array self, Function comparator) { - EnsoContext context = EnsoContext.get(this); - int size = self.getItems().length; - Object[] newArr = new Object[size]; - System.arraycopy(self.getItems(), 0, newArr, 0, size); - - try { - return getComparatorAndSort(state, newArr, comparator, context); - } catch (CompareException e) { - return DataflowError.withoutTrace( - incomparableValuesError(e.leftOperand, e.rightOperand), this); - } - } - - @Specialization(guards = "arrays.hasArrayElements(self)") - Object doPolyglotArray( - State state, - Object self, - Function comparator, - @CachedLibrary(limit = "3") InteropLibrary arrays, - @Cached HostValueToEnsoNode hostValueToEnsoNode) { - long size; - Object[] newArray; - try { - size = arrays.getArraySize(self); - newArray = new Object[(int) size]; - for (int i = 0; i < size; i++) { - newArray[i] = hostValueToEnsoNode.execute(arrays.readArrayElement(self, i)); - } - } catch (UnsupportedMessageException | InvalidArrayIndexException e) { - throw new IllegalStateException(e); - } - - try { - return getComparatorAndSort(state, newArray, comparator, EnsoContext.get(this)); - } catch (CompareException e) { - return DataflowError.withoutTrace( - incomparableValuesError(e.leftOperand, e.rightOperand), this); - } - } - - @Fallback - Object doOther(State state, Object self, Object comparator) { - CompilerDirectives.transferToInterpreter(); - var fun = EnsoContext.get(this).getBuiltins().function(); - throw new PanicException( - EnsoContext.get(this).getBuiltins().error().makeTypeError(fun, comparator, "comparator"), - this); - } - - private Object getComparatorAndSort( - State state, Object[] rawItems, Function comparator, EnsoContext context) { - Comparator compare = new SortComparator(comparator, context, this, state); - runSort(compare, rawItems); - return new Array(rawItems); - } - - Object[] runSort(Comparator compare, Object[] self) { - doSort(self, compare); - LoopNode.reportLoopCount(this, self.length); - return self; - } - - @TruffleBoundary - void doSort(Object[] items, Comparator compare) { - Arrays.sort(items, compare); - } - - private Object incomparableValuesError(Object left, Object right) { - return EnsoContext.get(this).getBuiltins().error().makeIncomparableValues(left, right); - } - - private class SortComparator implements Comparator { - private final Function compFn; - private final EnsoContext context; - private final Atom less; - private final Atom equal; - private final Atom greater; - private final SortNode outerThis; - private final State state; - - SortComparator(Function compFn, EnsoContext context, SortNode outerThis, State state) { - this.compFn = compFn; - this.context = context; - this.less = context.getBuiltins().ordering().newLess(); - this.equal = context.getBuiltins().ordering().newEqual(); - this.greater = context.getBuiltins().ordering().newGreater(); - this.outerThis = outerThis; - this.state = state; - } - - @Override - public int compare(Object o1, Object o2) { - Object res = callOptimiserNode.executeDispatch(compFn, null, state, new Object[] {o1, o2}); - if (res == less) { - return -1; - } else if (res == equal) { - return 0; - } else if (res == greater) { - return 1; - } else { - invalidCompareResultProfile.enter(); - throw new CompareException(o1, o2); - } - } - } - - private static final class CompareException extends RuntimeException { - final Object leftOperand; - final Object rightOperand; - - private CompareException(Object leftOperand, Object rightOperand) { - this.leftOperand = leftOperand; - this.rightOperand = rightOperand; - } - } -} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/GreaterNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/GreaterNode.java index 963c004ec802..ea3bb9ab4074 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/GreaterNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/GreaterNode.java @@ -34,9 +34,9 @@ boolean doBigInteger(EnsoBigInteger self, EnsoBigInteger that) { } @Fallback - DataflowError doOther(EnsoBigInteger self, Object that) { + Object doOther(EnsoBigInteger self, Object that) { var builtins = EnsoContext.get(this).getBuiltins(); - var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that"); - return DataflowError.withoutTrace(typeError, this); + var incomparableValsErr = builtins.error().makeIncomparableValues(self, that); + return DataflowError.withoutTrace(incomparableValsErr, this); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/GreaterOrEqualNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/GreaterOrEqualNode.java index c615457d1a15..2e7ec44d239e 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/GreaterOrEqualNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/GreaterOrEqualNode.java @@ -34,9 +34,9 @@ boolean doBigInteger(EnsoBigInteger self, EnsoBigInteger that) { } @Fallback - DataflowError doOther(EnsoBigInteger self, Object that) { + Object doOther(EnsoBigInteger self, Object that) { var builtins = EnsoContext.get(this).getBuiltins(); - var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that"); - return DataflowError.withoutTrace(typeError, this); + var incomparableValsErr = builtins.error().makeIncomparableValues(self, that); + return DataflowError.withoutTrace(incomparableValsErr, this); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/LessNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/LessNode.java index 71e4a55e74ac..1e145ba5b4c4 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/LessNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/LessNode.java @@ -34,9 +34,9 @@ boolean doBigInteger(EnsoBigInteger self, EnsoBigInteger that) { } @Fallback - DataflowError doOther(EnsoBigInteger self, Object that) { + Object doOther(EnsoBigInteger self, Object that) { var builtins = EnsoContext.get(this).getBuiltins(); - var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that"); - return DataflowError.withoutTrace(typeError, this); + var incomparableValsErr = builtins.error().makeIncomparableValues(self, that); + return DataflowError.withoutTrace(incomparableValsErr, this); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/LessOrEqualNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/LessOrEqualNode.java index 5fd408ba79f7..c1c71ec8b352 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/LessOrEqualNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/bigInteger/LessOrEqualNode.java @@ -34,9 +34,9 @@ boolean doBigInteger(EnsoBigInteger self, EnsoBigInteger that) { } @Fallback - DataflowError doOther(EnsoBigInteger self, Object that) { + Object doOther(EnsoBigInteger self, Object that) { var builtins = EnsoContext.get(this).getBuiltins(); - var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that"); - return DataflowError.withoutTrace(typeError, this); + var incomparableValsErr = builtins.error().makeIncomparableValues(self, that); + return DataflowError.withoutTrace(incomparableValsErr, this); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/GreaterNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/GreaterNode.java index cf1d301edb4d..4be5d441099f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/GreaterNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/GreaterNode.java @@ -6,6 +6,7 @@ import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps; import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.error.DataflowError; import org.enso.interpreter.runtime.number.EnsoBigInteger; @@ -19,24 +20,40 @@ static GreaterNode build() { } @Specialization - boolean doDouble(double self, double that) { - return self > that; + Object doDouble(double self, double that) { + if (Double.isNaN(self) || Double.isNaN(that)) { + return incomparableError(self, that); + } else { + return self > that; + } } @Specialization - boolean doLong(double self, long that) { - return self > (double) that; + Object doLong(double self, long that) { + if (Double.isNaN(self)) { + return incomparableError(self, that); + } else { + return self > (double) that; + } } @Specialization - boolean doBigInteger(double self, EnsoBigInteger that) { - return self > BigIntegerOps.toDouble(that.getValue()); + Object doBigInteger(double self, EnsoBigInteger that) { + if (Double.isNaN(self)) { + return incomparableError(self, that); + } else { + return self > BigIntegerOps.toDouble(that.getValue()); + } } @Fallback - DataflowError doOther(double self, Object that) { + Object doOther(double self, Object that) { + return incomparableError(self, that); + } + + private DataflowError incomparableError(Object self, Object that) { var builtins = EnsoContext.get(this).getBuiltins(); - var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that"); - return DataflowError.withoutTrace(typeError, this); + var incomparableErr = builtins.error().makeIncomparableValues(self, that); + return DataflowError.withoutTrace(incomparableErr, this); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/GreaterOrEqualNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/GreaterOrEqualNode.java index 00683e4cf399..0fac10d57671 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/GreaterOrEqualNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/GreaterOrEqualNode.java @@ -6,6 +6,7 @@ import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps; import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.error.DataflowError; import org.enso.interpreter.runtime.number.EnsoBigInteger; @@ -19,24 +20,40 @@ static GreaterOrEqualNode build() { } @Specialization - boolean doDouble(double self, double that) { - return self >= that; + Object doDouble(double self, double that) { + if (Double.isNaN(self) || Double.isNaN(that)) { + return incomparableError(self, that); + } else { + return self >= that; + } } @Specialization - boolean doLong(double self, long that) { - return self >= (double) that; + Object doLong(double self, long that) { + if (Double.isNaN(self)) { + return incomparableError(self, that); + } else { + return self >= (double) that; + } } @Specialization - boolean doBigInteger(double self, EnsoBigInteger that) { - return self >= BigIntegerOps.toDouble(that.getValue()); + Object doBigInteger(double self, EnsoBigInteger that) { + if (Double.isNaN(self)) { + return incomparableError(self, that); + } else { + return self >= BigIntegerOps.toDouble(that.getValue()); + } } @Fallback - DataflowError doOther(double self, Object that) { + Object doOther(double self, Object that) { + return incomparableError(self, that); + } + + private DataflowError incomparableError(Object self, Object that) { var builtins = EnsoContext.get(this).getBuiltins(); - var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that"); - return DataflowError.withoutTrace(typeError, this); + var incomparableErr = builtins.error().makeIncomparableValues(self, that); + return DataflowError.withoutTrace(incomparableErr, this); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/LessNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/LessNode.java index 914f28259971..437dc5a854d6 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/LessNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/LessNode.java @@ -6,6 +6,7 @@ import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps; import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.error.DataflowError; import org.enso.interpreter.runtime.number.EnsoBigInteger; @@ -19,24 +20,40 @@ static LessNode build() { } @Specialization - boolean doDouble(double self, double that) { - return self < that; + Object doDouble(double self, double that) { + if (Double.isNaN(self) || Double.isNaN(that)) { + return incomparableError(self, that); + } else { + return self < that; + } } @Specialization - boolean doLong(double self, long that) { - return self < (double) that; + Object doLong(double self, long that) { + if (Double.isNaN(self)) { + return incomparableError(self, that); + } else { + return self < (double) that; + } } @Specialization - boolean doBigInteger(double self, EnsoBigInteger that) { - return self < BigIntegerOps.toDouble(that.getValue()); + Object doBigInteger(double self, EnsoBigInteger that) { + if (Double.isNaN(self)) { + return incomparableError(self, that); + } else { + return self < BigIntegerOps.toDouble(that.getValue()); + } } @Fallback - DataflowError doOther(double self, Object that) { + Object doOther(double self, Object that) { + return incomparableError(self, that); + } + + private DataflowError incomparableError(Object self, Object that) { var builtins = EnsoContext.get(this).getBuiltins(); - var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that"); - return DataflowError.withoutTrace(typeError, this); + var incomparableErr = builtins.error().makeIncomparableValues(self, that); + return DataflowError.withoutTrace(incomparableErr, this); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/LessOrEqualNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/LessOrEqualNode.java index 9e58ffed5818..e52a14a6b306 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/LessOrEqualNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/decimal/LessOrEqualNode.java @@ -6,6 +6,7 @@ import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps; import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.error.DataflowError; import org.enso.interpreter.runtime.number.EnsoBigInteger; @@ -19,24 +20,40 @@ static LessOrEqualNode build() { } @Specialization - boolean doDouble(double self, double that) { - return self <= that; + Object doDouble(double self, double that) { + if (Double.isNaN(self) || Double.isNaN(that)) { + return incomparableError(self, that); + } else { + return self <= that; + } } @Specialization - boolean doLong(double self, long that) { - return self <= (double) that; + Object doLong(double self, long that) { + if (Double.isNaN(self)) { + return incomparableError(self, that); + } else { + return self <= (double) that; + } } @Specialization - boolean doBigInteger(double self, EnsoBigInteger that) { - return self <= BigIntegerOps.toDouble(that.getValue()); + Object doBigInteger(double self, EnsoBigInteger that) { + if (Double.isNaN(self)) { + return incomparableError(self, that); + } else { + return self <= BigIntegerOps.toDouble(that.getValue()); + } } @Fallback - DataflowError doOther(double self, Object that) { + Object doOther(double self, Object that) { + return incomparableError(self, that); + } + + private DataflowError incomparableError(Object self, Object that) { var builtins = EnsoContext.get(this).getBuiltins(); - var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that"); - return DataflowError.withoutTrace(typeError, this); + var incomparableErr = builtins.error().makeIncomparableValues(self, that); + return DataflowError.withoutTrace(incomparableErr, this); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/GreaterNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/GreaterNode.java index e444e925bac4..690d7bfc3c21 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/GreaterNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/GreaterNode.java @@ -33,9 +33,9 @@ boolean doBigInteger(long self, EnsoBigInteger that) { } @Fallback - DataflowError doOther(long self, Object that) { + Object doOther(long self, Object that) { var builtins = EnsoContext.get(this).getBuiltins(); - var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that"); - return DataflowError.withoutTrace(typeError, this); + var incomparableValsErr = builtins.error().makeIncomparableValues(self, that); + return DataflowError.withoutTrace(incomparableValsErr, this); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/GreaterOrEqualNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/GreaterOrEqualNode.java index dcb316b7ff6d..0e54883b476d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/GreaterOrEqualNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/GreaterOrEqualNode.java @@ -33,9 +33,9 @@ boolean doBigInteger(long self, EnsoBigInteger that) { } @Fallback - DataflowError doOther(long self, Object that) { + Object doOther(long self, Object that) { var builtins = EnsoContext.get(this).getBuiltins(); - var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that"); - return DataflowError.withoutTrace(typeError, this); + var incomparableValsErr = builtins.error().makeIncomparableValues(self, that); + return DataflowError.withoutTrace(incomparableValsErr, this); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/LessNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/LessNode.java index 2bf8362b6ee3..598251aaa2bf 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/LessNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/LessNode.java @@ -33,9 +33,9 @@ boolean doBigInteger(long self, EnsoBigInteger that) { } @Fallback - DataflowError doOther(long self, Object that) { + Object doOther(long self, Object that) { var builtins = EnsoContext.get(this).getBuiltins(); - var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that"); - return DataflowError.withoutTrace(typeError, this); + var incomparableValsErr = builtins.error().makeIncomparableValues(self, that); + return DataflowError.withoutTrace(incomparableValsErr, this); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/LessOrEqualNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/LessOrEqualNode.java index e74a28297f9d..089d31fc0fc9 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/LessOrEqualNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/number/smallInteger/LessOrEqualNode.java @@ -33,9 +33,9 @@ boolean doBigInteger(long self, EnsoBigInteger that) { } @Fallback - DataflowError doOther(long self, Object that) { + Object doOther(long self, Object that) { var builtins = EnsoContext.get(this).getBuiltins(); - var typeError = builtins.error().makeTypeError(builtins.number().getNumber(), that, "that"); - return DataflowError.withoutTrace(typeError, this); + var incomparableValsErr = builtins.error().makeIncomparableValues(self, that); + return DataflowError.withoutTrace(incomparableValsErr, this); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/ordering/LessThanNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/ordering/LessThanNode.java index f9406681732f..ba6bd3fe2fc2 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/ordering/LessThanNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/ordering/LessThanNode.java @@ -116,21 +116,13 @@ Object lessDoubleBigInt(double self, EnsoBigInteger other) { @Specialization @TruffleBoundary boolean lessLongBigInt(long self, EnsoBigInteger other) { - if (BigIntegerOps.fitsInLong(other.getValue())) { - return BigInteger.valueOf(self).compareTo(other.getValue()) < 0; - } else { - return true; - } + return BigInteger.valueOf(self).compareTo(other.getValue()) < 0; } @Specialization @TruffleBoundary boolean lessBigIntLong(EnsoBigInteger self, long other) { - if (BigIntegerOps.fitsInLong(self.getValue())) { - return self.getValue().compareTo(BigInteger.valueOf(other)) < 0; - } else { - return false; - } + return self.getValue().compareTo(BigInteger.valueOf(other)) < 0; } /** diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/ordering/SortArrayNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/ordering/SortArrayNode.java new file mode 100644 index 000000000000..1ed278790603 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/ordering/SortArrayNode.java @@ -0,0 +1,29 @@ +package org.enso.interpreter.node.expression.builtin.ordering; + +import com.oracle.truffle.api.nodes.Node; +import org.enso.interpreter.dsl.AcceptsError; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.runtime.state.State; + +/** Just a wrapper for {@code Array.sort_builtin} that delegates to {@code Vector.sort_builtin}. */ +@BuiltinMethod(type = "Array", name = "sort_builtin") +public final class SortArrayNode extends Node { + @Child private SortVectorNode sortVectorNode = SortVectorNode.build(); + + public Object execute( + State state, + @AcceptsError Object self, + long ascending, + Object comparators, + Object compareFunctions, + Object byFunc, + Object onFunc, + long problemBehavior) { + return sortVectorNode.execute( + state, self, ascending, comparators, compareFunctions, byFunc, onFunc, problemBehavior); + } + + public static SortArrayNode build() { + return new SortArrayNode(); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/ordering/SortVectorNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/ordering/SortVectorNode.java new file mode 100644 index 000000000000..3a77eb80fa7a --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/ordering/SortVectorNode.java @@ -0,0 +1,804 @@ +package org.enso.interpreter.node.expression.builtin.ordering; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.GenerateUncached; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.InvalidArrayIndexException; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.nodes.Node; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; +import org.enso.interpreter.dsl.AcceptsError; +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.node.callable.dispatch.CallOptimiserNode; +import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode; +import org.enso.interpreter.node.expression.builtin.meta.EqualsNode; +import org.enso.interpreter.node.expression.builtin.meta.TypeOfNode; +import org.enso.interpreter.node.expression.builtin.text.AnyToTextNode; +import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.data.Array; +import org.enso.interpreter.runtime.data.ArrayRope; +import org.enso.interpreter.runtime.data.Type; +import org.enso.interpreter.runtime.data.Vector; +import org.enso.interpreter.runtime.data.text.Text; +import org.enso.interpreter.runtime.error.DataflowError; +import org.enso.interpreter.runtime.error.PanicException; +import org.enso.interpreter.runtime.error.Warning; +import org.enso.interpreter.runtime.error.WarningsLibrary; +import org.enso.interpreter.runtime.error.WithWarnings; +import org.enso.interpreter.runtime.state.State; + +/** + * Sorts a vector with elements that have only Default_Comparator, thus, only elements with a + * builtin type, which is the most common scenario for sorting. + */ +@BuiltinMethod(type = "Vector", name = "sort_builtin", description = "Returns a sorted vector.") +@GenerateUncached +public abstract class SortVectorNode extends Node { + + public static SortVectorNode build() { + return SortVectorNodeGen.create(); + } + + /** + * Sorts a vector with elements that have only Default_Comparator, thus, only builtin types. + * + * @param self Vector that has elements with only Default_Comparator, that are elements with + * builtin types. + * @param ascending -1 for descending, 1 for ascending + * @param comparators Vector of comparators, with the same length of self. This is gather in the + * Enso code, because doing that in this builtin would be difficult. If {@code onFunc} + * parameter is not {@code Nothing}, comparators are gathered from the result of {@code + * onFunc} projection. + * @param compareFunctions Vector of `Comparator.compare` functions gathered from the comparators + * @param byFunc If Nothing, then the default `by` function should be used. The default `by` + * function is `Ordering.compare`. + * @param onFunc If Nothing, then the default identity function should be used. + * @param problemBehavior A long representation of `Problem_Behavior`. Ignore is 0, Report_warning + * is 1, and Report_Error is 2. + * @return A new, sorted vector. + */ + public abstract Object execute( + State state, + @AcceptsError Object self, + long ascending, + Object comparators, + Object compareFunctions, + Object byFunc, + Object onFunc, + long problemBehavior); + + /** + * Sorts primitive values, i.e., values with only Default_Comparator. We can optimize this case. + * It is important that `byFunc` is Nothing, i.e., has the default value. In that case, we can + * hard code the partial ordering for the primitive values. If `byFunc` is a custom user function, + * it can redefine the default partial ordering of the primitive values, which requires + * topological sort. + */ + @Specialization( + guards = { + "interop.hasArrayElements(self)", + "areAllDefaultComparators(interop, hostValueToEnsoNode, comparators)", + "interop.isNull(byFunc)", + "interop.isNull(onFunc)" + }) + Object sortPrimitives( + State state, + Object self, + long ascending, + Object comparators, + Object compareFunctions, + Object byFunc, + Object onFunc, + long problemBehavior, + @Cached LessThanNode lessThanNode, + @Cached EqualsNode equalsNode, + @Cached HostValueToEnsoNode hostValueToEnsoNode, + @Cached TypeOfNode typeOfNode, + @Cached AnyToTextNode toTextNode, + @CachedLibrary(limit = "10") InteropLibrary interop) { + EnsoContext ctx = EnsoContext.get(this); + Object[] elems; + try { + long size = interop.getArraySize(self); + assert size < Integer.MAX_VALUE; + elems = new Object[(int) size]; + for (int i = 0; i < size; i++) { + if (interop.isArrayElementReadable(self, i)) { + elems[i] = hostValueToEnsoNode.execute(interop.readArrayElement(self, i)); + } else { + CompilerDirectives.transferToInterpreter(); + throw new PanicException( + ctx.getBuiltins() + .error() + .makeUnsupportedArgumentsError( + new Object[] {self}, + "Cannot read array element at index " + i + " of " + self), + this); + } + } + } catch (UnsupportedMessageException | InvalidArrayIndexException e) { + throw new IllegalStateException("Should not reach here", e); + } + var javaComparator = + createDefaultComparator( + lessThanNode, equalsNode, typeOfNode, toTextNode, ascending, problemBehavior, interop); + try { + return sortPrimitiveVector(elems, javaComparator); + } catch (CompareException e) { + return DataflowError.withoutTrace( + incomparableValuesError(e.leftOperand, e.rightOperand), this); + } + } + + @TruffleBoundary + private DefaultSortComparator createDefaultComparator( + LessThanNode lessThanNode, + EqualsNode equalsNode, + TypeOfNode typeOfNode, + AnyToTextNode toTextNode, + long ascending, + long problemBehaviorNum, + InteropLibrary interop) { + return new DefaultSortComparator( + lessThanNode, + equalsNode, + typeOfNode, + toTextNode, + ascending > 0, + ProblemBehavior.fromInt((int) problemBehaviorNum), + interop); + } + + @TruffleBoundary + @Specialization( + guards = { + "interop.hasArrayElements(self)", + }) + Object sortGeneric( + State state, + Object self, + long ascending, + Object comparatorsArray, + Object compareFuncsArray, + Object byFunc, + Object onFunc, + long problemBehaviorNum, + @CachedLibrary(limit = "10") InteropLibrary interop, + @CachedLibrary(limit = "5") WarningsLibrary warningsLib, + @Cached LessThanNode lessThanNode, + @Cached EqualsNode equalsNode, + @Cached TypeOfNode typeOfNode, + @Cached AnyToTextNode toTextNode, + @Cached(value = "build()", uncached = "build()") HostValueToEnsoNode hostValueToEnsoNode, + @Cached(value = "build()", uncached = "build()") CallOptimiserNode callNode) { + var problemBehavior = ProblemBehavior.fromInt((int) problemBehaviorNum); + // Split into groups + List elems = readInteropArray(interop, hostValueToEnsoNode, warningsLib, self); + List comparators = + readInteropArray(interop, hostValueToEnsoNode, warningsLib, comparatorsArray); + List compareFuncs = + readInteropArray(interop, hostValueToEnsoNode, warningsLib, compareFuncsArray); + List groups = splitByComparators(elems, comparators, compareFuncs); + + // Prepare input for DefaultSortComparator and GenericSortComparator and sort the elements + // within groups + var ctx = EnsoContext.get(this); + Atom less = ctx.getBuiltins().ordering().newLess(); + Atom equal = ctx.getBuiltins().ordering().newEqual(); + Atom greater = ctx.getBuiltins().ordering().newGreater(); + Set gatheredWarnings = new HashSet<>(); + List resultVec = new ArrayList<>(); + try { + for (var group : groups) { + SortComparator javaComparator; + if (interop.isNull(byFunc) && interop.isNull(onFunc) && isPrimitiveGroup(group)) { + javaComparator = + new DefaultSortComparator( + lessThanNode, + equalsNode, + typeOfNode, + toTextNode, + ascending > 0, + problemBehavior, + interop); + } else { + Object compareFunc = interop.isNull(byFunc) ? group.compareFunc : byFunc; + javaComparator = + new GenericSortComparator( + ascending > 0, + compareFunc, + onFunc, + problemBehavior, + group.comparator, + callNode, + toTextNode, + state, + less, + equal, + greater, + interop); + } + group.elems.sort(javaComparator); + if (javaComparator.hasWarnings()) { + gatheredWarnings.addAll(javaComparator.getEncounteredWarnings()); + } + resultVec.addAll(group.elems); + } + var sortedVector = Vector.fromArray(new Array(resultVec.toArray())); + // Attach gathered warnings along with different comparators warning + switch (problemBehavior) { + case REPORT_ERROR -> { + if (groups.size() > 1) { + assert groups.get(0).elems.size() > 0 : "Groups should not be empty"; + assert groups.get(1).elems.size() > 0 : "Groups should not be empty"; + var firstIncomparableElem = groups.get(0).elems.get(0); + var secondIncomparableElem = groups.get(1).elems.get(0); + var err = + ctx.getBuiltins() + .error() + .makeIncomparableValues(firstIncomparableElem, secondIncomparableElem); + return DataflowError.withoutTrace(err, this); + } else { + // Just one comparator, different from Default_Comparator + if (!gatheredWarnings.isEmpty()) { + throw new UnsupportedOperationException("unimplemented"); + } else { + return sortedVector; + } + } + } + case REPORT_WARNING -> { + return attachDifferentComparatorsWarning( + attachWarnings(sortedVector, gatheredWarnings), groups); + } + case IGNORE -> { + return sortedVector; + } + default -> throw new IllegalStateException("unreachable"); + } + } catch (CompareException e) { + return DataflowError.withoutTrace( + incomparableValuesError(e.leftOperand, e.rightOperand), this); + } + } + + @TruffleBoundary(allowInlining = true) + private Object sortPrimitiveVector(Object[] elems, DefaultSortComparator javaComparator) + throws CompareException { + Arrays.sort(elems, javaComparator); + var sortedVector = Vector.fromArray(new Array(elems)); + + if (javaComparator.hasWarnings()) { + return attachWarnings(sortedVector, javaComparator.getEncounteredWarnings()); + } else { + return sortedVector; + } + } + + private List splitByComparators( + List elements, List comparators, List compareFuncs) { + assert elements.size() == comparators.size(); + assert elements.size() == compareFuncs.size(); + // Mapping of FQN of comparator to groups + Map groupMap = new HashMap<>(); + for (int i = 0; i < elements.size(); i++) { + Object elem = elements.get(i); + Type comparator = comparators.get(i); + Function compareFunc = compareFuncs.get(i); + String qualifiedName = comparator.getQualifiedName().toString(); + + if (!groupMap.containsKey(qualifiedName)) { + groupMap.put(qualifiedName, new Group(new ArrayList<>(), comparator, compareFunc)); + } + var group = groupMap.get(qualifiedName); + group.elems.add(elem); + } + + // Sort groups by the FQN of their comparator, with the default comparator + // being the first one (the first group). + String defCompFQN = getDefaultComparatorQualifiedName(); + return groupMap.entrySet().stream() + .sorted( + (entry1, entry2) -> { + var fqn1 = entry1.getKey(); + var fqn2 = entry2.getKey(); + if (fqn1.equals(defCompFQN)) { + return -1; + } else if (fqn2.equals(defCompFQN)) { + return 1; + } else { + return fqn1.compareTo(fqn2); + } + }) + .map(Entry::getValue) + .collect(Collectors.toList()); + } + + private Object attachWarnings(Object vector, Set warnings) { + var warnArray = + warnings.stream() + .map(Text::create) + .map(text -> Warning.create(EnsoContext.get(this), text, this)) + .toArray(Warning[]::new); + return WithWarnings.appendTo(vector, new ArrayRope<>(warnArray)); + } + + private Object attachDifferentComparatorsWarning(Object vector, List groups) { + var diffCompsMsg = + groups.stream() + .map(Group::comparator) + .map(comparator -> comparator.getQualifiedName().toString()) + .collect(Collectors.joining(", ")); + var text = Text.create("Different comparators: [" + diffCompsMsg + "]"); + var warn = Warning.create(EnsoContext.get(this), text, this); + return WithWarnings.appendTo(vector, new ArrayRope<>(warn)); + } + + private String getDefaultComparatorQualifiedName() { + return EnsoContext.get(this) + .getBuiltins() + .defaultComparator() + .getType() + .getQualifiedName() + .toString(); + } + + /** A group is "primitive" iff its comparator is the default comparator. */ + private boolean isPrimitiveGroup(Group group) { + return group + .comparator + .getQualifiedName() + .toString() + .equals(getDefaultComparatorQualifiedName()); + } + + private Object incomparableValuesError(Object left, Object right) { + return EnsoContext.get(this).getBuiltins().error().makeIncomparableValues(left, right); + } + + /** + * Helper slow-path method to conveniently gather elements from interop arrays into a java list + */ + @SuppressWarnings("unchecked") + private List readInteropArray( + InteropLibrary interop, + HostValueToEnsoNode hostValueToEnsoNode, + WarningsLibrary warningsLib, + Object vector) { + try { + int size = (int) interop.getArraySize(vector); + List res = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + Object elem = hostValueToEnsoNode.execute(interop.readArrayElement(vector, i)); + if (warningsLib.hasWarnings(elem)) { + elem = warningsLib.removeWarnings(elem); + } + res.add((T) elem); + } + return res; + } catch (UnsupportedMessageException | InvalidArrayIndexException | ClassCastException e) { + throw new IllegalStateException("Should not be reachable", e); + } + } + + private int getBuiltinTypeCost(Object builtinType) { + assert isBuiltinType(builtinType); + var builtins = EnsoContext.get(this).getBuiltins(); + if (builtinType == builtins.number().getNumber() + || builtinType == builtins.number().getInteger() + || builtinType == builtins.number().getDecimal()) { + return 1; + } else if (builtinType == builtins.text()) { + return 2; + } else if (builtinType == builtins.bool().getType()) { + return 3; + } else if (builtinType == builtins.date()) { + return 4; + } else if (builtinType == builtins.dateTime()) { + return 5; + } else if (builtinType == builtins.duration()) { + return 6; + } else if (builtinType == builtins.vector()) { + // vectors are incomparable, but we want to sort them before Nothings and NaNs. + return 50; + } else { + // Type is not a builtin type + throw new IllegalStateException("Should be a builtin type: " + builtinType); + } + } + + private boolean isBuiltinType(Object type) { + var builtins = EnsoContext.get(this).getBuiltins(); + return builtins.number().getNumber() == type + || builtins.number().getDecimal() == type + || builtins.number().getInteger() == type + || builtins.nothing() == type + || builtins.text() == type + || builtins.bool().getType() == type + || builtins.date() == type + || builtins.dateTime() == type + || builtins.duration() == type + || builtins.vector() == type; + } + + private boolean isTrue(Object object) { + return Boolean.TRUE.equals(object); + } + + /** Returns true iff the given array of comparators is all Default_Comparator */ + boolean areAllDefaultComparators( + InteropLibrary interop, HostValueToEnsoNode hostValueToEnsoNode, Object comparators) { + assert interop.hasArrayElements(comparators); + var ctx = EnsoContext.get(this); + try { + int compSize = (int) interop.getArraySize(comparators); + for (int i = 0; i < compSize; i++) { + assert interop.isArrayElementReadable(comparators, i); + Object comparator = hostValueToEnsoNode.execute(interop.readArrayElement(comparators, i)); + if (!isDefaultComparator(comparator, ctx)) { + return false; + } + } + } catch (UnsupportedMessageException | InvalidArrayIndexException e) { + throw new IllegalStateException("Should not be reachable", e); + } + return true; + } + + boolean isDefaultComparator(Object object, EnsoContext ctx) { + return ctx.getBuiltins().defaultComparator().getType() == object; + } + + private boolean isNan(Object object) { + return object instanceof Double dbl && dbl.isNaN(); + } + + private enum ProblemBehavior { + IGNORE, + REPORT_WARNING, + REPORT_ERROR; + + static ProblemBehavior fromInt(int intValue) { + return switch (intValue) { + case 0 -> IGNORE; + case 1 -> REPORT_WARNING; + case 2 -> REPORT_ERROR; + default -> throw new IllegalArgumentException(Integer.toString(intValue)); + }; + } + } + + /** + * Group of elements grouped by comparator. + * + * @param elems Elements of the group. + * @param comparator SortComparator for the elems, i.e., it should hold that {@code elems.each + * it-> (Comparable.from it) == comparator}. + * @param compareFunc `SortComparator.compare` function extracted from the comparator. + */ + private record Group(List elems, Type comparator, Function compareFunc) {} + + /** + * Convenient base class that implements java.util.Comparator, and gathers warnings about + * incomparable values. The warnings are gathered as pure Strings in a hash set, so that they are + * not duplicated. + */ + private abstract static class SortComparator implements java.util.Comparator { + + private final Set warnings = new HashSet<>(); + final AnyToTextNode toTextNode; + final ProblemBehavior problemBehavior; + final InteropLibrary interop; + + protected SortComparator( + AnyToTextNode toTextNode, ProblemBehavior problemBehavior, InteropLibrary interop) { + this.toTextNode = toTextNode; + this.problemBehavior = problemBehavior; + this.interop = interop; + } + + @TruffleBoundary + protected void attachIncomparableValuesWarning(Object x, Object y) { + var xStr = toTextNode.execute(x).toString(); + var yStr = toTextNode.execute(y).toString(); + String warnText = "Values " + xStr + " and " + yStr + " are incomparable"; + warnings.add(warnText); + } + + public Set getEncounteredWarnings() { + return warnings; + } + + @TruffleBoundary + public boolean hasWarnings() { + return !warnings.isEmpty(); + } + } + + /** + * SortComparator for comparing all values that have Default_Comparator. These are either + * primitive types, or the types that do not provide their own comparator. + * + *

Note that it is essential for this class that the {@code by} method parameter to {@code + * Vector.sort} is set to the default value, which is {@code Ordering.compare}, because then, we + * know that the partial ordering for primitive types was not redefined by the user (we handle + * partial ordering for primitive types specifically, partial ordering for other types is not + * implemented yet - that requires topological sorting). + */ + final class DefaultSortComparator extends SortComparator { + + private final LessThanNode lessThanNode; + private final EqualsNode equalsNode; + private final TypeOfNode typeOfNode; + private final boolean ascending; + + private DefaultSortComparator( + LessThanNode lessThanNode, + EqualsNode equalsNode, + TypeOfNode typeOfNode, + AnyToTextNode toTextNode, + boolean ascending, + ProblemBehavior problemBehavior, + InteropLibrary interop) { + super(toTextNode, problemBehavior, interop); + this.lessThanNode = lessThanNode; + this.equalsNode = equalsNode; + this.typeOfNode = typeOfNode; + this.ascending = ascending; + } + + @Override + public int compare(Object x, Object y) { + return compareValuesWithDefaultComparator(x, y); + } + + int compareValuesWithDefaultComparator(Object x, Object y) { + if (equalsNode.execute(x, y)) { + return 0; + } else { + // Check if x < y + Object xLessThanYRes = lessThanNode.execute(x, y); + if (interop.isNull(xLessThanYRes)) { + // x and y are incomparable - this can happen if x and y are different types + return handleIncomparableValues(x, y); + } else if (isTrue(xLessThanYRes)) { + return ascending ? -1 : 1; + } else { + // Check if x > y + Object yLessThanXRes = lessThanNode.execute(y, x); + if (isTrue(yLessThanXRes)) { + return ascending ? 1 : -1; + } else { + // yLessThanXRes is either Nothing or False + return handleIncomparableValues(y, x); + } + } + } + } + + private int handleIncomparableValues(Object x, Object y) { + switch (problemBehavior) { + case REPORT_ERROR -> throw new CompareException(x, y); + case REPORT_WARNING -> attachIncomparableValuesWarning(x, y); + } + if (isPrimitiveValue(x) || isPrimitiveValue(y)) { + if (isPrimitiveValue(x) && isPrimitiveValue(y)) { + return handleIncomparablePrimitives(x, y); + } else if (isPrimitiveValue(x)) { + // Primitive values are always before non-primitive values - Default_Comparator + // group should be the first one. + return ascending ? -1 : 1; + } else if (isPrimitiveValue(y)) { + return ascending ? 1 : -1; + } else { + throw new IllegalStateException("Should not be reachable"); + } + } else { + // Values other than primitives are compared just by their type's FQN. + var xTypeName = getQualifiedTypeName(x); + var yTypeName = getQualifiedTypeName(y); + return xTypeName.compareTo(yTypeName); + } + } + + /** + * Incomparable primitive values have either different builtin types, or are Nothing or NaN. All + * these cases are handled specifically - we hardcode the order of these incomparable values. + */ + private int handleIncomparablePrimitives(Object x, Object y) { + int xCost = getPrimitiveValueCost(x); + int yCost = getPrimitiveValueCost(y); + int res = Integer.compare(xCost, yCost); + return ascending ? res : -res; + } + + private boolean isPrimitiveValue(Object object) { + return isBuiltinType(typeOfNode.execute(object)); + } + + private String getQualifiedTypeName(Object object) { + var typeObj = typeOfNode.execute(object); + return toTextNode.execute(typeObj).toString(); + } + + private int getPrimitiveValueCost(Object object) { + if (interop.isNull(object)) { + return 200; + } else if (isNan(object)) { + return 100; + } else { + var type = typeOfNode.execute(object); + return getBuiltinTypeCost(type); + } + } + } + + /** + * Comparator for any values. This comparator compares the values by calling back to Enso (by + * {@link #compareFunc}), rather than using compare nodes (i.e. {@link LessThanNode}). directly, + * as opposed to {@link DefaultSortComparator}. + */ + private final class GenericSortComparator extends SortComparator { + + private final boolean ascending; + /** + * Either function from `by` parameter to the `Vector.sort` method, or the `compare` function + * extracted from the comparator for the appropriate group. + */ + private final Function compareFunc; + + private final Function onFunc; + private final boolean hasCustomOnFunc; + private final Type comparator; + private final CallOptimiserNode callNode; + private final State state; + private final Atom less; + private final Atom equal; + private final Atom greater; + + private GenericSortComparator( + boolean ascending, + Object compareFunc, + Object onFunc, + ProblemBehavior problemBehavior, + Type comparator, + CallOptimiserNode callNode, + AnyToTextNode toTextNode, + State state, + Atom less, + Atom equal, + Atom greater, + InteropLibrary interop) { + super(toTextNode, problemBehavior, interop); + assert compareFunc != null; + assert comparator != null; + this.comparator = comparator; + this.state = state; + this.ascending = ascending; + this.compareFunc = checkAndConvertByFunc(compareFunc); + if (interop.isNull(onFunc)) { + this.hasCustomOnFunc = false; + this.onFunc = null; + } else { + this.hasCustomOnFunc = true; + this.onFunc = checkAndConvertOnFunc(onFunc); + } + this.callNode = callNode; + this.less = less; + this.equal = equal; + this.greater = greater; + } + + @Override + public int compare(Object x, Object y) { + Object xConverted; + Object yConverted; + if (hasCustomOnFunc) { + // onFunc cannot have `self` argument, we assume it has just one argument. + xConverted = callNode.executeDispatch(onFunc, null, state, new Object[] {x}); + yConverted = callNode.executeDispatch(onFunc, null, state, new Object[] {y}); + } else { + xConverted = x; + yConverted = y; + } + Object[] args; + if (hasFunctionSelfArgument(compareFunc)) { + args = new Object[] {comparator, xConverted, yConverted}; + } else { + args = new Object[] {xConverted, yConverted}; + } + Object res = callNode.executeDispatch(compareFunc, null, state, args); + if (res == less) { + return ascending ? -1 : 1; + } else if (res == equal) { + return 0; + } else if (res == greater) { + return ascending ? 1 : -1; + } else { + // res is either Nothing, or Incomparable_Values. Either way, it means that x and y are + // incomparable. + // This case is not supported yet, as it requires topological sorting. + // We cannot detect if the result was actually returned from the default comparator (it + // could have been transitively called), so we just bailout. + throw new CompareException(x, y); + } + } + + private boolean hasFunctionSelfArgument(Function function) { + if (function.getSchema().getArgumentsCount() > 0) { + return function.getSchema().getArgumentInfos()[0].getName().equals("self"); + } else { + return false; + } + } + + /** + * Checks value given for {@code by} parameter and converts it to {@link Function}. Throw a + * dataflow error otherwise. + */ + private Function checkAndConvertByFunc(Object byFuncObj) { + return checkAndConvertFunction( + byFuncObj, "Unsupported argument for `by`, expected a method with two arguments", 2, 3); + } + + /** + * Checks the value given for {@code on} parameter and converts it to {@link Function}. Throws a + * dataflow error otherwise. + */ + private Function checkAndConvertOnFunc(Object onFuncObj) { + return checkAndConvertFunction( + onFuncObj, "Unsupported argument for `on`, expected a method with one argument", 1, 1); + } + + /** + * @param minArgCount Minimal count of arguments without a default value. + * @param maxArgCount Maximal count of argument without a default value. + */ + private Function checkAndConvertFunction( + Object funcObj, String errMsg, int minArgCount, int maxArgCount) { + var err = new IllegalArgumentException(errMsg + ", got " + funcObj); + if (funcObj instanceof Function func) { + var argCount = getNumberOfNonDefaultArguments(func); + if (minArgCount <= argCount && argCount <= maxArgCount) { + return func; + } else { + throw err; + } + } else { + throw err; + } + } + } + + private static int getNumberOfNonDefaultArguments(Function function) { + return (int) + Arrays.stream(function.getSchema().getArgumentInfos()) + .filter(argInfo -> !argInfo.hasDefaultValue()) + .count(); + } + + private static final class CompareException extends RuntimeException { + + final Object leftOperand; + final Object rightOperand; + + private CompareException(Object leftOperand, Object rightOperand) { + this.leftOperand = leftOperand; + this.rightOperand = rightOperand; + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/AnyToTextNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/AnyToTextNode.java index 7f54a1b89838..878fdeed5f6c 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/AnyToTextNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/text/AnyToTextNode.java @@ -2,6 +2,7 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.dsl.Fallback; +import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.UnsupportedMessageException; @@ -14,12 +15,8 @@ import org.enso.interpreter.runtime.data.text.Text; @BuiltinMethod(type = "Any", name = "to_text", description = "Generic text conversion.") +@GenerateUncached public abstract class AnyToTextNode extends Node { - private static final int DISPATCH_CACHE = 3; - private @Child InteropLibrary displays = - InteropLibrary.getFactory().createDispatched(DISPATCH_CACHE); - private @Child InteropLibrary strings = - InteropLibrary.getFactory().createDispatched(DISPATCH_CACHE); public static AnyToTextNode build() { return AnyToTextNodeGen.create(); @@ -28,19 +25,22 @@ public static AnyToTextNode build() { public abstract Text execute(Object self); @Specialization - Text doAtom(Atom at, @CachedLibrary(limit = "10") StructsLibrary structs) { + Text doAtom( + Atom at, + @CachedLibrary(limit = "10") StructsLibrary structs, + @CachedLibrary(limit = "10") InteropLibrary interop) { var fields = structs.getFields(at); if (fields.length == 0) { return consName(at.getConstructor()); } else { - return doComplexAtom(at, fields); + return doComplexAtom(at, fields, interop); } } @Fallback - Text doOther(Object object) { + Text doOther(Object object, @CachedLibrary(limit = "5") InteropLibrary interop) { try { - return Text.create(showObject(object)); + return Text.create(showObject(object, interop)); } catch (UnsupportedMessageException e) { CompilerDirectives.transferToInterpreter(); return Text.create(object.toString()); @@ -53,18 +53,18 @@ private Text consName(AtomConstructor constructor) { } @CompilerDirectives.TruffleBoundary - private Text doComplexAtom(Atom atom, Object[] fields) { + private Text doComplexAtom(Atom atom, Object[] fields, InteropLibrary interop) { Text res = Text.create("(", consName(atom.getConstructor())); res = Text.create(res, " "); try { - res = Text.create(res, showObject(fields[0])); + res = Text.create(res, showObject(fields[0], interop)); } catch (UnsupportedMessageException e) { res = Text.create(res, fields[0].toString()); } for (int i = 1; i < fields.length; i++) { res = Text.create(res, " "); try { - res = Text.create(res, showObject(fields[i])); + res = Text.create(res, showObject(fields[i], interop)); } catch (UnsupportedMessageException e) { res = Text.create(res, fields[i].toString()); } @@ -74,7 +74,8 @@ private Text doComplexAtom(Atom atom, Object[] fields) { } @CompilerDirectives.TruffleBoundary - private String showObject(Object child) throws UnsupportedMessageException { + private String showObject(Object child, InteropLibrary interop) + throws UnsupportedMessageException { if (child == null) { // TODO [RW] This is a temporary workaround to make it possible to display errors related to // https://www.pivotaltracker.com/story/show/181652974 @@ -83,7 +84,7 @@ private String showObject(Object child) throws UnsupportedMessageException { } else if (child instanceof Boolean) { return (boolean) child ? "True" : "False"; } else { - return strings.asString(displays.toDisplayString(child)); + return interop.asString(interop.toDisplayString(child)); } } } diff --git a/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java b/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java index f36036b2e117..4330e59119c3 100644 --- a/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java +++ b/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java @@ -113,6 +113,8 @@ private Value createEnsoMethod(String source, String methodName) { @Test public void recursiveFactorialCall() { final Value facFn = createEnsoMethod(""" + from Standard.Base.Data.Ordering import all + fac : Number -> Number fac n = facacc : Number -> Number -> Number diff --git a/engine/runtime/src/test/java/org/enso/interpreter/test/TestBase.java b/engine/runtime/src/test/java/org/enso/interpreter/test/TestBase.java index 8c39fff912c0..4394b4959136 100644 --- a/engine/runtime/src/test/java/org/enso/interpreter/test/TestBase.java +++ b/engine/runtime/src/test/java/org/enso/interpreter/test/TestBase.java @@ -116,6 +116,17 @@ protected static Value evalModule(Context ctx, String src) { return mainMethod.execute(); } + /** + * Parses the given module and returns a method by the given name from the module. + * + * @param moduleSrc Source of the whole module + * @return Reference to the method. + */ + protected static Value getMethodFromModule(Context ctx, String moduleSrc, String methodName) { + Value module = ctx.eval(Source.create("enso", moduleSrc)); + return module.invokeMember(Module.EVAL_EXPRESSION, methodName); + } + /** * An artificial RootNode. Used for tests of nodes that need to be adopted. Just create this root * node inside a context, all the other nodes, and insert them via {@link diff --git a/engine/runtime/src/test/java/org/enso/interpreter/test/ValuesGenerator.java b/engine/runtime/src/test/java/org/enso/interpreter/test/ValuesGenerator.java index b4bde2934528..f8aca03bf393 100644 --- a/engine/runtime/src/test/java/org/enso/interpreter/test/ValuesGenerator.java +++ b/engine/runtime/src/test/java/org/enso/interpreter/test/ValuesGenerator.java @@ -301,10 +301,17 @@ public List numbers() { collect.add(v(null, "", "42").type()); collect.add(v(null, "", "6.7").type()); collect.add(v(null, "", "40321 * 43202").type()); - collect.add(v(null, """ + collect.add( + v( + null, + """ + from Standard.Base.Data.Ordering import all + fac s n = if n <= 1 then s else @Tail_Call fac n*s n-1 - """, "fac 1 100").type()); + """, + "fac 1 100") + .type()); collect.add(v(null, "", "123 * 10^40").type()); collect.add(v(null, "", "123 * 10^40 + 0.0").type()); collect.add(v(null, "", "123 * 10^40 + 1.0").type()); diff --git a/engine/runtime/src/test/java/org/enso/interpreter/test/VectorSortTest.java b/engine/runtime/src/test/java/org/enso/interpreter/test/VectorSortTest.java new file mode 100644 index 000000000000..cceda051e360 --- /dev/null +++ b/engine/runtime/src/test/java/org/enso/interpreter/test/VectorSortTest.java @@ -0,0 +1,100 @@ +package org.enso.interpreter.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import org.enso.interpreter.test.ValuesGenerator.Language; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; +import org.junit.AfterClass; +import org.junit.Assume; +import org.junit.BeforeClass; +import org.junit.experimental.theories.DataPoints; +import org.junit.experimental.theories.Theories; +import org.junit.experimental.theories.Theory; +import org.junit.runner.RunWith; + +/** + * This test ensures that any combination of values can be sorted - no attempt to sort should fail. + */ +@RunWith(Theories.class) +public class VectorSortTest extends TestBase { + private static Context context; + private static Value sortFunc; + private static Value equalsFunc; + + @BeforeClass + public static void initCtxAndNodes() { + context = createDefaultContext(); + var code = + """ + from Standard.Base import all + + sort val1 val2 = [val1, val2].sort + equals val1 val2 = val1 == val2 + """; + sortFunc = getMethodFromModule(context, code, "sort"); + equalsFunc = getMethodFromModule(context, code, "equals"); + + values = new ArrayList<>(); + var valuesGenerator = + ValuesGenerator.create(context, Language.ENSO, Language.JAVA, Language.JAVASCRIPT); + values.addAll(valuesGenerator.numbers()); + values.addAll(valuesGenerator.vectors()); + values.addAll(valuesGenerator.arrayLike()); + values.addAll(valuesGenerator.booleans()); + values.addAll(valuesGenerator.durations()); + values.addAll(valuesGenerator.maps()); + } + + @AfterClass + public static void disposeCtx() { + context.close(); + } + + @DataPoints public static List values; + + @Theory + public void testSortHandlesAllValues(Value value1, Value value2) { + Assume.assumeFalse(isNan(value1) || isNan(value2)); + Value res = sortFunc.execute(value1, value2); + assertTrue(res.hasArrayElements()); + assertEquals(2, res.getArraySize()); + List resArray = readPolyglotArray(res); + // check that value1 is there unchanged on some index, and the same for value2 + assertTrue( + "Sorted vector should contain the first value at any index", + invokeEquals(value1, resArray.get(0)) || invokeEquals(value1, resArray.get(1))); + assertTrue( + "Sorted vector should contain the second value at any index", + invokeEquals(value2, resArray.get(0)) || invokeEquals(value2, resArray.get(1))); + } + + private boolean isNan(Value value) { + if (value.isNumber() && value.fitsInDouble()) { + return Double.isNaN(value.asDouble()); + } else { + return false; + } + } + + private List readPolyglotArray(Value array) { + assertTrue(array.hasArrayElements()); + assertTrue(array.hasIterator()); + Value iterator = array.getIterator(); + assertTrue(iterator.isIterator()); + List res = new ArrayList<>(); + while (iterator.hasIteratorNextElement()) { + res.add(iterator.getIteratorNextElement()); + } + return res; + } + + private boolean invokeEquals(Value val1, Value val2) { + Value res = equalsFunc.execute(val1, val2); + assertTrue("Result from Any.== should be boolean", res.isBoolean()); + return res.asBoolean(); + } +} diff --git a/test/Table_Tests/src/In_Memory/Table_Spec.enso b/test/Table_Tests/src/In_Memory/Table_Spec.enso index 3361aeaaf6dd..78b2fa0a6457 100644 --- a/test/Table_Tests/src/In_Memory/Table_Spec.enso +++ b/test/Table_Tests/src/In_Memory/Table_Spec.enso @@ -472,7 +472,7 @@ spec = c = Column.from_vector 'foo' [My.Data 1 2, My.Data 2 5, My.Data 3 4, My.Data 6 3, Nothing, My.Data 1 0] cmp a b = Ordering.compare (a.x-a.y).abs (b.x-b.y).abs r = c.sort by=cmp - r.to_vector.should_equal [My.Data 1 2, My.Data 3 4, My.Data 1 0, My.Data 2 5, My.Data 6 3, Nothing] + r.to_vector.should_equal [Nothing, My.Data 1 2, My.Data 3 4, My.Data 1 0, My.Data 2 5, My.Data 6 3] d = Column.from_vector 'foo' [1,3,2,4,Nothing,5] cmp2 a b = Ordering.compare -a -b diff --git a/test/Tests/src/Data/Array_Spec.enso b/test/Tests/src/Data/Array_Spec.enso index 3df0bf8c154c..86581f37d9f0 100644 --- a/test/Tests/src/Data/Array_Spec.enso +++ b/test/Tests/src/Data/Array_Spec.enso @@ -52,10 +52,7 @@ spec = Test.group "Compare functionality with Vector" <| Test.specify "compare methods" <| vector_methods = Meta.meta Vector . methods . sort - array_methods_all = Meta.meta Array . methods . sort - - array_methods = array_methods_all.filter (name -> name != "sort_builtin") - + array_methods = Meta.meta Array . methods . sort vector_methods . should_equal array_methods Test.group "ArrayOverBuffer" <| diff --git a/test/Tests/src/Data/Numbers_Spec.enso b/test/Tests/src/Data/Numbers_Spec.enso index e6c9e28034ed..314d948806a1 100644 --- a/test/Tests/src/Data/Numbers_Spec.enso +++ b/test/Tests/src/Data/Numbers_Spec.enso @@ -1,6 +1,6 @@ from Standard.Base import all import Standard.Base.Errors.Common.Arithmetic_Error -import Standard.Base.Errors.Common.Type_Error +import Standard.Base.Errors.Common.Incomparable_Values from Standard.Base.Data.Numbers import Number_Parse_Error @@ -297,21 +297,21 @@ spec = (1 < 0.99).should_be_false (3 < hundred_factorial).should_be_true (3 < very_negative).should_be_false - (3 < Nothing).should_fail_with Type_Error + (3 < Nothing).should_fail_with Incomparable_Values (1.01 < 0.99).should_be_false (1.01 < 1.02).should_be_true (1.01 < 1).should_be_false (1.01 < 2).should_be_true (3.14 < hundred_factorial).should_be_true (3.14 < very_negative).should_be_false - (1.5 < Nothing).should_fail_with Type_Error + (1.5 < Nothing).should_fail_with Incomparable_Values (hundred_factorial < 1).should_be_false (hundred_factorial < 1.5).should_be_false (very_negative < 1).should_be_true (very_negative < 1.5).should_be_true (hundred_factorial < very_negative).should_be_false (very_negative < hundred_factorial).should_be_true - (very_negative < Nothing).should_fail_with Type_Error + (very_negative < Nothing).should_fail_with Incomparable_Values Test.specify "should support less than or equal to operator" <| (1 <= 2).should_be_true @@ -321,21 +321,21 @@ spec = (1 <= 0.99).should_be_false (3 <= hundred_factorial).should_be_true (3 <= very_negative).should_be_false - (3 <= Nothing).should_fail_with Type_Error + (3 <= Nothing).should_fail_with Incomparable_Values (1.01 <= 0.99).should_be_false (1.01 <= 1.02).should_be_true (1.01 <= 1).should_be_false (1.01 <= 2).should_be_true (3.14 <= hundred_factorial).should_be_true (3.14 <= very_negative).should_be_false - (1.5 <= Nothing).should_fail_with Type_Error + (1.5 <= Nothing).should_fail_with Incomparable_Values (hundred_factorial <= 1).should_be_false (hundred_factorial <= 1.5).should_be_false (very_negative <= 1).should_be_true (very_negative <= 1.5).should_be_true (hundred_factorial <= very_negative).should_be_false (very_negative <= hundred_factorial).should_be_true - (very_negative <= Nothing).should_fail_with Type_Error + (very_negative <= Nothing).should_fail_with Incomparable_Values Test.specify "should support greater than operator" <| (1 > 2).should_be_false @@ -345,21 +345,21 @@ spec = (1 > 0.99).should_be_true (3 > hundred_factorial).should_be_false (3 > very_negative).should_be_true - (3 > Nothing).should_fail_with Type_Error + (3 > Nothing).should_fail_with Incomparable_Values (1.01 > 0.99).should_be_true (1.01 > 1.02).should_be_false (1.01 > 1).should_be_true (1.01 > 2).should_be_false (3.14 > hundred_factorial).should_be_false (3.14 > very_negative).should_be_true - (1.5 > Nothing).should_fail_with Type_Error + (1.5 > Nothing).should_fail_with Incomparable_Values (hundred_factorial > 1).should_be_true (hundred_factorial > 1.5).should_be_true (very_negative > 1).should_be_false (very_negative > 1.5).should_be_false (hundred_factorial > very_negative).should_be_true (very_negative > hundred_factorial).should_be_false - (very_negative > Nothing).should_fail_with Type_Error + (very_negative > Nothing).should_fail_with Incomparable_Values Test.specify "should support greater than or equal to operator" <| (1 >= 2).should_be_false @@ -369,21 +369,21 @@ spec = (1 >= 0.99).should_be_true (3 >= hundred_factorial).should_be_false (3 >= very_negative).should_be_true - (3 >= Nothing).should_fail_with Type_Error + (3 >= Nothing).should_fail_with Incomparable_Values (1.01 >= 0.99).should_be_true (1.01 >= 1.02).should_be_false (1.01 >= 1).should_be_true (1.01 >= 2).should_be_false (3.14 >= hundred_factorial).should_be_false (3.14 >= very_negative).should_be_true - (1.5 >= Nothing).should_fail_with Type_Error + (1.5 >= Nothing).should_fail_with Incomparable_Values (hundred_factorial >= 1).should_be_true (hundred_factorial >= 1.5).should_be_true (very_negative >= 1).should_be_false (very_negative >= 1.5).should_be_false (hundred_factorial >= very_negative).should_be_true (very_negative >= hundred_factorial).should_be_false - (very_negative >= Nothing).should_fail_with Type_Error + (very_negative >= Nothing).should_fail_with Incomparable_Values Test.specify "should be ordered by the default comparator" <| Ordering.compare 1 2 . should_equal Ordering.Less @@ -393,21 +393,21 @@ spec = Ordering.compare 1 0.99 . should_equal Ordering.Greater Ordering.compare 3 hundred_factorial . should_equal Ordering.Less Ordering.compare 3 very_negative . should_equal Ordering.Greater - Ordering.compare 3 Nothing . should_fail_with Type_Error + Ordering.compare 3 Nothing . should_fail_with Incomparable_Values Ordering.compare 1.01 0.99 . should_equal Ordering.Greater Ordering.compare 1.01 1.02 . should_equal Ordering.Less Ordering.compare 1.01 1 . should_equal Ordering.Greater Ordering.compare 1.01 2 . should_equal Ordering.Less Ordering.compare 3.14 hundred_factorial . should_equal Ordering.Less Ordering.compare 3.14 very_negative . should_equal Ordering.Greater - Ordering.compare 1.5 Nothing . should_fail_with Type_Error + Ordering.compare 1.5 Nothing . should_fail_with Incomparable_Values Ordering.compare hundred_factorial 1 . should_equal Ordering.Greater Ordering.compare hundred_factorial 1.5 . should_equal Ordering.Greater Ordering.compare very_negative 1 . should_equal Ordering.Less Ordering.compare very_negative 1.5 . should_equal Ordering.Less Ordering.compare hundred_factorial very_negative . should_equal Ordering.Greater Ordering.compare very_negative hundred_factorial . should_equal Ordering.Less - Ordering.compare very_negative Nothing . should_fail_with Type_Error + Ordering.compare very_negative Nothing . should_fail_with Incomparable_Values Test.specify "should expose exponentiation operations" <| (3.14 ^ 2.71).should_equal 22.216689546 epsilon=eps @@ -467,7 +467,7 @@ spec = Number.negative_infinity . should_equal (-Number.positive_infinity) Number.negative_infinity . equals (-Number.positive_infinity) . should_be_true - Number.nan . equals Number.nan . should_be_false - Number.nan . equals 0 . should_be_false + Number.nan . equals Number.nan . should_fail_with Incomparable_Values + Number.nan . equals 0 . should_fail_with Incomparable_Values main = Test_Suite.run_main spec diff --git a/test/Tests/src/Data/Ordering_Spec.enso b/test/Tests/src/Data/Ordering_Spec.enso index 2701603b2e7a..098e44f34cc3 100644 --- a/test/Tests/src/Data/Ordering_Spec.enso +++ b/test/Tests/src/Data/Ordering_Spec.enso @@ -2,7 +2,7 @@ from Standard.Base import all import Standard.Base.Errors.Common.Incomparable_Values import Standard.Base.Errors.Common.Type_Error -from Standard.Test import Test, Test_Suite +from Standard.Test import Test, Test_Suite, Problems import Standard.Test.Extensions # === Test Resources === @@ -16,7 +16,18 @@ type Ord_Comparator Comparable.from (_:Ord) = Ord_Comparator -## Unordered pair + +type My_Type + Value val + +type My_Type_Comparator + compare x y = (Comparable.from x.val).compare x.val y.val + hash x = (Comparable.from x.val).hash x.val + +Comparable.from (_:My_Type) = My_Type_Comparator + + +## Unordered pair - its `compare` method returns either `Nothing` or `Ordering.Equal`. type UPair Value x y @@ -36,9 +47,33 @@ Comparable.from (_ : UPair) = UPair_Comparator type Parent Value child -# === The Tests === +# Just a type without custom comparator +type No_Comp_Type + Value val + +## Expects that `result` contains incomparable values warning. + The values within the warning message can be switched - the order + does not matter. Iterates through all the warnings of result. +expect_incomparable_warn : Any -> Any -> Any -> Nothing +expect_incomparable_warn left_val right_val result = + # Incomparable values warning wraps Text values in simple quotes + left_val_text = left_val.pretty + right_val_text = right_val.pretty + expected_warn_msg_left = "Values " + left_val_text + " and " + right_val_text + " are incomparable" + expected_warn_msg_right = "Values " + right_val_text + " and " + left_val_text + " are incomparable" + has_expected_warning = Warning.get_all result . map (_.value) . any (it-> it == expected_warn_msg_left || it == expected_warn_msg_right) + has_expected_warning . should_be_true + +expect_no_warns : Any -> Nothing +expect_no_warns result = + Warning.get_all result . length . should_equal 0 + + +# === The Tests === spec = + topo_sort_pending = "Waiting for implementation of topological sort (https://github.com/enso-org/enso/issues/5834)" + Test.group "Default comparator" <| Test.specify "should support custom comparator" <| Ordering.compare (Ord.Value 1) (Ord.Value 2) . should_equal Ordering.Less @@ -50,9 +85,9 @@ spec = ((Parent.Value (Ord.Value 1)) == (Parent.Value (Ord.Value 1))) . should_be_true ((Parent.Value (Ord.Value 1)) == (Parent.Value (Ord.Value 22))) . should_be_false - Test.specify "should throw Type_Error when comparing different types" <| - Ordering.compare (UPair.Value 1 2) (Ord.Value 2) . should_fail_with Type_Error - Ordering.compare 1 Nothing . should_fail_with Type_Error + Test.specify "should throw Incomparable_Values when comparing different types" <| + Ordering.compare (UPair.Value 1 2) (Ord.Value 2) . should_fail_with Incomparable_Values + Ordering.compare 1 Nothing . should_fail_with Incomparable_Values Test.group "Ordering" <| Test.specify "should allow conversion to sign representation" <| @@ -93,10 +128,106 @@ spec = Ordering.compare 42.5 67.9 . should_equal Ordering.Less Meta.is_same_object (Comparable.from Number.nan) (Comparable.from 42.0) . should_be_true - Test.specify "should fail with Type_Error for wrong type of that" <| - Ordering.compare Ordering.Less 1 . should_fail_with Type_Error - Ordering.compare Ordering.Less Nothing . should_fail_with Type_Error - Ordering.compare Ordering.Less "Hello" . should_fail_with Type_Error + Test.specify "should fail with Incomparable_Values for wrong type of that" <| + Ordering.compare Ordering.Less 1 . should_fail_with Incomparable_Values + Ordering.compare Ordering.Less Nothing . should_fail_with Incomparable_Values + Ordering.compare Ordering.Less "Hello" . should_fail_with Incomparable_Values + + Test.group "Sorting with the default comparator" <| + + Test.specify "should be able to sort primitive types" <| + [3, 2, 1, Nothing].sort . should_equal [1, 2, 3, Nothing] + [Nothing, Number.nan].sort . at 0 . is_nan . should_be_true + [Nothing, Number.nan].sort . at 1 . is_nothing . should_be_true + [3, 2.5].sort . should_equal [2.5, 3] + ["hello", 3].sort . should_equal [3, "hello"] + ["hello", "ahoj", 3].sort . should_equal [3, "ahoj", "hello"] + ["hello", "ahoj", 3, 2].sort . should_equal [2, 3, "ahoj", "hello"] + ["hello", "ahoj", Number.nan, 3].sort . take 3 . should_equal [3, "ahoj", "hello"] + ["hello", "ahoj", Number.nan, 3].sort . at 3 . is_nan . should_be_true + [100, Date.new 2020, 50].sort . should_equal [50, 100, Date.new 2020] + [100, Nothing, Date.new 2020, 50].sort . should_equal [50, 100, Date.new 2020, Nothing] + [3, 2, True, False].sort . should_equal [2, 3, False, True] + [3, True, 2, False].sort . should_equal [2, 3, False, True] + [Nothing, False].sort . should_equal [False, Nothing] + + Test.specify "should be able to sort any single-element vector without any warnings" <| + [Nothing].sort . should_equal [Nothing] + expect_no_warns [Nothing].sort + [[Nothing]].sort . should_equal [[Nothing]] + expect_no_warns [[Nothing]].sort + [[1]].sort . should_equal [[1]] + expect_no_warns [[1]].sort + + Test.specify "should produce warnings when sorting nested vectors" <| + [[1], [2]].sort . should_equal [[1], [2]] + [[2], [1]].sort . should_equal [[2], [1]] + + Test.specify "should be able to sort primitive values in atoms" pending=topo_sort_pending <| + [Ord.Value Nothing, Ord.Value 20, Ord.Value 10].sort . should_equal [Ord.Value 10, Ord.Value 20, Ord.Value Nothing] + + Test.specify "should produce warnings when sorting primitive values in atoms" pending=topo_sort_pending <| + expect_incomparable_warn (Ord.Value 1) (Ord.Value Nothing) [Ord.Value 1, Ord.Value Nothing].sort + + Test.specify "should fail to sort custom incomparable values until topological sorting is implemented" <| + [(UPair.Value 1 2), (UPair.Value 3 4)].sort . should_fail_with Incomparable_Values + + Test.specify "should attach warning when trying to sort incomparable values" <| + expect_incomparable_warn Nothing Number.nan <| [Nothing, Number.nan].sort on_incomparable=Problem_Behavior.Report_Warning + expect_incomparable_warn 1 "hello" <| [1, "hello"].sort on_incomparable=Problem_Behavior.Report_Warning + + Test.specify "should respect previous warnings on a vector" <| + Problems.expect_warning "my_warn" <| (Warning.attach "my_warn" [3, 2]) . sort + Problems.expect_warning "my_warn" <| (Warning.attach "my_warn" [3, Number.nan]) . sort + expect_incomparable_warn 3 Number.nan <| (Warning.attach "my_warn" [3, Number.nan]) . sort on_incomparable=Problem_Behavior.Report_Warning + + Test.specify "should respect previous warnings on vectors" pending="https://github.com/enso-org/enso/issues/6070" <| + Problems.expect_warning "my_warn" <| [3, Warning.attach "my_warn" 2].sort + expect_incomparable_warn 1 Number.nan [1, Warning.attach "my_warn" Number.nan].sort + Problems.expect_warning "my_warn" <| [1, Warning.attach "my_warn" Number.nan].sort + + Test.specify "should not fail when sorting incomparable types without custom comparator" <| + # Parent, and No_Comp_Type do not have custom comparators + [No_Comp_Type.Value 42, "hello"].sort . should_equal ["hello", No_Comp_Type.Value 42] + [Parent.Value 42, No_Comp_Type.Value 42].sort . should_equal [No_Comp_Type.Value 42, Parent.Value 42] + [No_Comp_Type.Value 42, Parent.Value 42].sort . should_equal [No_Comp_Type.Value 42, Parent.Value 42] + + Test.group "Sorting with multiple comparators" <| + Test.specify "should sort primitive values with the default comparator as the first group" <| + [Ord.Value 4, Ord.Value 3, 20, 10].sort . should_equal [10, 20, Ord.Value 3, Ord.Value 4] + [Ord.Value 4, 20, Ord.Value 3, 10].sort . should_equal [10, 20, Ord.Value 3, Ord.Value 4] + [20, Ord.Value 4, Ord.Value 3, 10].sort . should_equal [10, 20, Ord.Value 3, Ord.Value 4] + [Ord.Value 4, 20, Ord.Value 3, 10].sort . should_equal [10, 20, Ord.Value 3, Ord.Value 4] + [Nothing, Ord.Value 4, 20, Ord.Value 3, 10].sort . should_equal [10, 20, Nothing, Ord.Value 3, Ord.Value 4] + [Ord.Value 4, 20, Ord.Value 3, Nothing, 10].sort . should_equal [10, 20, Nothing, Ord.Value 3, Ord.Value 4] + + Test.specify "should produce warning when sorting types with different comparators" <| + [Ord.Value 1, 1].sort . should_equal [1, Ord.Value 1] + sorted = [Ord.Value 1, 1].sort on_incomparable=Problem_Behavior.Report_Warning + Warning.get_all sorted . at 0 . value . starts_with "Different comparators" . should_be_true + + Test.specify "should merge groups of values with custom comparators based on the comparators FQN" <| + [Ord.Value 1, My_Type.Value 1].sort . should_equal [My_Type.Value 1, Ord.Value 1] + [My_Type.Value 1, Ord.Value 1].sort . should_equal [My_Type.Value 1, Ord.Value 1] + sorted = [Ord.Value 1, My_Type.Value 1].sort on_incomparable=Problem_Behavior.Report_Warning + Warning.get_all sorted . at 0 . value . starts_with "Different comparators" . should_be_true + + Test.specify "should be stable when sorting values with different comparators" <| + [Ord.Value 1, 20, My_Type.Value 1, 10].sort . should_equal [10, 20, My_Type.Value 1, Ord.Value 1] + [20, Ord.Value 1, My_Type.Value 1, 10].sort . should_equal [10, 20, My_Type.Value 1, Ord.Value 1] + [20, My_Type.Value 1, Ord.Value 1, 10].sort . should_equal [10, 20, My_Type.Value 1, Ord.Value 1] + [20, 10, My_Type.Value 1, Ord.Value 1].sort . should_equal [10, 20, My_Type.Value 1, Ord.Value 1] + [My_Type.Value 1, Ord.Value 1, 20, 10].sort . should_equal [10, 20, My_Type.Value 1, Ord.Value 1] + [Ord.Value 1, 20, 10, My_Type.Value 1].sort . should_equal [10, 20, My_Type.Value 1, Ord.Value 1] + + Test.specify "should be able to sort even unordered values" pending=topo_sort_pending <| + [Ord.Value 2, UPair.Value "a" "b", Ord.Value 1, UPair.Value "c" "d"].sort . should_equal [Ord.Value 2, Ord.Value 1, UPair.Value "a" "b", UPair.Value "c" "d"] + [Ord.Value 2, UPair.Value "X" "Y", Ord.Value 1, UPair.Value "c" "d"].sort . should_equal [Ord.Value 2, Ord.Value 1, UPair.Value "X" "Y", UPair.Value "c" "d"] + + Test.specify "should produce warning when sorting unordered values" pending=topo_sort_pending <| + expect_incomparable_warn (UPair.Value 1 2) (UPair.Value 3 4) [UPair.Value 1 2, UPair.Value 3 4].sort + + main = Test_Suite.run_main spec diff --git a/test/Tests/src/Data/Vector_Spec.enso b/test/Tests/src/Data/Vector_Spec.enso index 72d971cfe8e3..e5cd7c8c4886 100644 --- a/test/Tests/src/Data/Vector_Spec.enso +++ b/test/Tests/src/Data/Vector_Spec.enso @@ -557,11 +557,8 @@ type_spec name alter = Test.group name <| alter [Time_Of_Day.new 12, Time_Of_Day.new 10 30] . sort . should_equal [Time_Of_Day.new 10 30, Time_Of_Day.new 12] alter [Date_Time.new 2000 12 30 12 30, Date_Time.new 2000 12 30 12 00] . sort . should_equal [Date_Time.new 2000 12 30 12 00, Date_Time.new 2000 12 30 12 30] - alter ["aa", 2] . sort . should_fail_with Incomparable_Values - alter [2, Date.new 1999] . sort . should_fail_with Incomparable_Values - alter [Date_Time.new 1999 1 1 12 30, Date.new 1999] . sort . should_fail_with Incomparable_Values - alter [Date_Time.new 1999 1 1 12 30, Time_Of_Day.new 12 30] . sort . should_fail_with Incomparable_Values - Test.expect_panic_with ([3,2,1].to_array.sort_builtin 42) Type_Error + alter ["aa", 2] . sort . should_equal [2, "aa"] + alter [2, Date.new 1999] . sort . should_equal [2, Date.new 1999] Test.specify "should leave the original vector unchanged" <| non_empty_vec = alter [2, 4, 2, 3, 2, 3] @@ -574,6 +571,10 @@ type_spec name alter = Test.group name <| small_expected = [T.Value -20 0, T.Value -1 1, T.Value -1 10, T.Value 1 8, T.Value 1 3, T.Value 4 0] small_vec.sort . should_equal small_expected + Test.specify "should fail the sort if Report_Error problem_behavior specified" <| + alter [T.Value 1 8, Nothing] . sort on_incomparable=Problem_Behavior.Report_Error . should_fail_with Incomparable_Values + alter [Nothing, Number.nan] . sort on_incomparable=Problem_Behavior.Report_Error . should_fail_with Incomparable_Values + Test.specify "should be able to use a custom element projection" <| small_vec = alter [T.Value 1 8, T.Value 1 3, T.Value -20 0, T.Value -1 1, T.Value -1 10, T.Value 4 0] small_expected = [T.Value -20 0, T.Value 4 0, T.Value -1 1, T.Value 1 3, T.Value 1 8, T.Value -1 10] diff --git a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Any.enso b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Any.enso index 949fae265046..047341b8242c 100644 --- a/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Any.enso +++ b/test/micro-distribution/lib/Standard/Base/0.0.0-dev/src/Any.enso @@ -5,9 +5,13 @@ type Any catch_primitive handler = @Builtin_Method "Any.catch_primitive" to_text self = @Builtin_Method "Any.to_text" to_display_text self = @Builtin_Method "Any.to_display_text" - # Access EqualsNode directly - == self other = Comparable.equals_builtin self other is_error self = False + == self other = Comparable.equals_builtin self other + != self other = (self == other).not + < self other = Comparable.less_than_builtin self other + <= self other = Comparable.less_than_builtin self other || Comparable.equals_builtin self other + > self other = Comparable.less_than_builtin other self + >= self other = Comparable.less_than_builtin other self || Comparable.equals_builtin other self @Builtin_Type type Comparable diff --git a/tools/performance/engine-benchmarks/bench_download.py b/tools/performance/engine-benchmarks/bench_download.py index 1794b47ee0dc..8743c296e20a 100755 --- a/tools/performance/engine-benchmarks/bench_download.py +++ b/tools/performance/engine-benchmarks/bench_download.py @@ -588,16 +588,28 @@ def commit_to_str(commit: Commit) -> str: bench_run_2 = _parse_bench_run_from_json(res_2) bench_report_1 = get_bench_report(bench_run_1, cache, tmp_dir) bench_report_2 = get_bench_report(bench_run_2, cache, tmp_dir) - bench_labels: List[str] = list(bench_report_1.label_score_dict.keys()) + # Check that the runs have the same labels, and get their intersection + bench_labels_1 = set(bench_report_1.label_score_dict.keys()) + bench_labels_2 = set(bench_report_2.label_score_dict.keys()) + if bench_labels_1 != bench_labels_2: + logging.warning( + f"Benchmark labels are not the same in both runs. This means that " + f"there will be some missing numbers in one of the runs. " + f"The set difference is {bench_labels_1.difference(bench_labels_2)}") + all_labels: List[str] = sorted( + list(bench_labels_1.intersection(bench_labels_2))) + bench_report_2.label_score_dict.keys() + df = pd.DataFrame(columns=["bench_label", "score-run-1", "score-run-2"]) - for bench_label in bench_labels: + for bench_label in all_labels: df = pd.concat([df, pd.DataFrame([{ "bench_label": bench_label, "score-run-1": bench_report_1.label_score_dict[bench_label], "score-run-2": bench_report_2.label_score_dict[bench_label], }])], ignore_index=True) df["score-diff"] = np.diff(df[["score-run-1", "score-run-2"]], axis=1) - df["score-diff-perc"] = df.apply(lambda row: perc_str(percent_change(row["score-run-1"], row["score-run-2"])), + df["score-diff-perc"] = df.apply(lambda row: perc_str( + percent_change(row["score-run-1"], row["score-run-2"])), axis=1) print("================================") print(df.to_string(index=False, header=True, justify="center", float_format="%.5f"))