mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 07:12:20 +03:00
Convert Any.== to a builtin (#3956)
`Any.==` is a builtin method. The semantics is the same as it used to be, except that we no longer assume `x == y` iff `Meta.is_same_object x y`, which used to be the case and caused failures in table tests. # Important Notes Measurements from `EqualsBenchmarks` shows that the performance of `Any.==` for recursive atoms increased by roughly 20%, and the performance for primitive types stays roughly the same.
This commit is contained in:
parent
74742d3267
commit
e6838bc90d
@ -492,6 +492,7 @@
|
||||
- [Vector returns warnings of individual elements][3938]
|
||||
- [Enso.getMetaObject, Type.isMetaInstance and Meta.is_a consolidation][3949]
|
||||
- [Add executionContext/interrupt API command][3952]
|
||||
- [Any.== is a builtin method][3956]
|
||||
- [Simplify exception handling for polyglot exceptions][3981]
|
||||
|
||||
[3227]: https://github.com/enso-org/enso/pull/3227
|
||||
@ -568,6 +569,7 @@
|
||||
[3938]: https://github.com/enso-org/enso/pull/3938
|
||||
[3949]: https://github.com/enso-org/enso/pull/3949
|
||||
[3952]: https://github.com/enso-org/enso/pull/3952
|
||||
[3956]: https://github.com/enso-org/enso/pull/3956
|
||||
[3981]: https://github.com/enso-org/enso/pull/3981
|
||||
|
||||
# Enso 2.0.0-alpha.18 (2021-10-12)
|
||||
|
@ -74,11 +74,24 @@ type Any
|
||||
implementing equality for your own types, keep in mind that it needs to
|
||||
work with any Enso value as the `that` argument.
|
||||
|
||||
! Unicode Equality
|
||||
The definition of equality includes Unicode canonicalization. I.e. two
|
||||
texts are equal if they are identical after canonical decomposition. This
|
||||
ensures that different ways of expressing the same character in the
|
||||
underlying binary representation are considered equal.
|
||||
|
||||
? Generic Equality and Performance
|
||||
While the generic equality provided here will work for _all_ values in
|
||||
Enso, its performance may often be suboptimal. Many types can implement
|
||||
their own equality operations that will be more efficient than these.
|
||||
|
||||
> Example
|
||||
The string 'é' (i.e. the character U+00E9, LATIN SMALL LETTER E WITH ACUTE)
|
||||
is canonically the same as the string 'e\u0301' (i.e. the letter `e`
|
||||
followed by U+0301, COMBINING ACUTE ACCENT). Therefore:
|
||||
|
||||
('é' == 'e\u0301') == True
|
||||
|
||||
> Example
|
||||
Checking if the variable `a` is equal to `147`.
|
||||
|
||||
@ -88,27 +101,7 @@ type Any
|
||||
a = 7 * 21
|
||||
a == 147
|
||||
== : Any -> Boolean
|
||||
== self that = if Meta.is_same_object self that then True else
|
||||
self_meta = Meta.meta self
|
||||
that_meta = Meta.meta that
|
||||
case Pair.new self_meta that_meta of
|
||||
Pair.Value (Meta.Atom.Value _) (Meta.Atom.Value _) ->
|
||||
c_1 = self_meta.constructor.value ...
|
||||
c_2 = that_meta.constructor.value ...
|
||||
if Meta.is_same_object c_1 c_2 . not then False else
|
||||
f_1 = self_meta.fields
|
||||
f_2 = that_meta.fields
|
||||
0.up_to f_1.length . all i-> (f_1.at i) == (f_2.at i)
|
||||
Pair.Value (Meta.Error.Value _) (Meta.Error.Value _) -> self_meta.payload == that_meta.payload
|
||||
Pair.Value (Meta.Polyglot.Value o_1) (Meta.Polyglot.Value o_2) ->
|
||||
langs_match = (self_meta.get_language == Meta.Language.Java) && (that_meta.get_language == Meta.Language.Java)
|
||||
if langs_match.not then False else o_1.equals o_2
|
||||
Pair.Value (Meta.Unresolved_Symbol.Value _) (Meta.Unresolved_Symbol.Value _) ->
|
||||
(self_meta.name == that_meta.name) && (self_meta.scope == that_meta.scope)
|
||||
## Constructor comparison is covered by the identity equality.
|
||||
Primitive objects should define their own equality.
|
||||
Therefore, there are no more cases to handle in self method.
|
||||
_ -> False
|
||||
== self that = @Builtin_Method "Any.=="
|
||||
|
||||
## ALIAS Inequality
|
||||
|
||||
|
@ -154,18 +154,3 @@ type Array
|
||||
primitive array protocol.
|
||||
to_array : Array
|
||||
to_array self = @Builtin_Method "Array.to_array"
|
||||
|
||||
## Checks whether this array is equal to `that`.
|
||||
|
||||
Arguments:
|
||||
- that: The array to compare this array against.
|
||||
|
||||
Two arrays are considered equal, when they have the same length and
|
||||
their items are pairwise equal.
|
||||
|
||||
== : Array -> Boolean
|
||||
== self that =
|
||||
if Meta.is_same_object Array self then Meta.is_same_object Array that else
|
||||
eq_at i = self.at i == that.at i
|
||||
Panic.catch Any handler=(_ -> False)
|
||||
if self.length == that.length then 0.up_to self.length . all eq_at else False
|
||||
|
@ -14,18 +14,6 @@ type Boolean
|
||||
True
|
||||
False
|
||||
|
||||
## Compares two booleans for equality.
|
||||
|
||||
Arguments:
|
||||
- that: The boolean to compare this with.
|
||||
|
||||
> Example
|
||||
Comparing True to False to get False.
|
||||
|
||||
True == False
|
||||
== : Boolean -> Boolean
|
||||
== self that = @Builtin_Method "Boolean.=="
|
||||
|
||||
## Computes the logical and (conjunction) of two booleans.
|
||||
|
||||
Arguments:
|
||||
|
@ -420,9 +420,3 @@ type Locale
|
||||
example_to_text = Locale.default.to_text
|
||||
to_text : Text | Nothing
|
||||
to_text self = self.java_locale.toLanguageTag
|
||||
|
||||
## Compares two locales for equality.
|
||||
== : Any -> Boolean
|
||||
== self other = case other of
|
||||
Locale.Value other_java_locale -> self.java_locale.equals other_java_locale
|
||||
_ -> False
|
||||
|
@ -458,18 +458,6 @@ type Decimal
|
||||
^ : Number -> Number
|
||||
^ self that = @Builtin_Method "Decimal.^"
|
||||
|
||||
## Compares this and that for equality.
|
||||
|
||||
Arguments:
|
||||
- that: The number to compare this against.
|
||||
|
||||
> Example
|
||||
Comparing 7 and 2.1 for equality.
|
||||
|
||||
7 == 2.1
|
||||
== : Number -> Boolean
|
||||
== self that = @Builtin_Method "Decimal.=="
|
||||
|
||||
## Checks if this is greater than that.
|
||||
|
||||
Arguments:
|
||||
@ -708,18 +696,6 @@ type Integer
|
||||
^ : Number -> Number
|
||||
^ self that = @Builtin_Method "Integer.^"
|
||||
|
||||
## Compares this and that for equality.
|
||||
|
||||
Arguments:
|
||||
- that: The number to compare this against.
|
||||
|
||||
> Example
|
||||
Comparing 7 and 2 for equality.
|
||||
|
||||
7 == 2
|
||||
== : Number -> Boolean
|
||||
== self that = @Builtin_Method "Integer.=="
|
||||
|
||||
## Checks if this is greater than that.
|
||||
|
||||
Arguments:
|
||||
|
@ -33,29 +33,6 @@ type Text
|
||||
+ : Text -> Text
|
||||
+ self that = @Builtin_Method "Text.+"
|
||||
|
||||
## Checks whether `self` is equal to `that`.
|
||||
|
||||
Arguments:
|
||||
- that: The text to compare `self` for equality with.
|
||||
|
||||
! Unicode Equality
|
||||
The definition of equality includes Unicode canonicalization. I.e. two
|
||||
texts are equal if they are identical after canonical decomposition. This
|
||||
ensures that different ways of expressing the same character in the
|
||||
underlying binary representation are considered equal.
|
||||
|
||||
> Example
|
||||
The string 'é' (i.e. the character U+00E9, LATIN SMALL LETTER E WITH ACUTE)
|
||||
is canonically the same as the string 'e\u0301' (i.e. the letter `e`
|
||||
followed by U+0301, COMBINING ACUTE ACCENT). Therefore:
|
||||
|
||||
('é' == 'e\u0301') == True
|
||||
== : Any -> Boolean
|
||||
== self that = if Meta.is_same_object self Text then Meta.is_same_object that Text else
|
||||
case that of
|
||||
_ : Text -> Text_Utils.equals self that
|
||||
_ -> False
|
||||
|
||||
## Compare two texts to discover their ordering.
|
||||
|
||||
Arguments:
|
||||
|
@ -605,14 +605,6 @@ type Date
|
||||
Ordering.from_sign sign
|
||||
_ -> Error.throw (Type_Error.Error Date that "that")
|
||||
|
||||
## Compares two Dates for equality.
|
||||
== : Date -> Boolean
|
||||
== self that = case that of
|
||||
Date -> Meta.is_same_object self Date
|
||||
_ : Date ->
|
||||
sign = Time_Utils.compare_to_localdate self that
|
||||
0 == sign
|
||||
_ -> False
|
||||
|
||||
## PRIVATE
|
||||
week_days_between start end =
|
||||
|
@ -701,12 +701,3 @@ type Date_Time
|
||||
sign = Time_Utils.compare_to_zoneddatetime self that
|
||||
Ordering.from_sign sign
|
||||
_ -> Error.throw (Type_Error.Error Date_Time that "that")
|
||||
|
||||
## Compares two Date_Time for equality.
|
||||
== : Date_Time -> Boolean
|
||||
== self that = case that of
|
||||
Date_Time -> Meta.is_same_object self Date_Time
|
||||
_ : Date_Time ->
|
||||
sign = Time_Utils.compare_to_zoneddatetime self that
|
||||
0 == sign
|
||||
_ -> False
|
||||
|
@ -166,23 +166,6 @@ type Duration
|
||||
Panic.catch ArithmeticException (self.minus_builtin that) err->
|
||||
Error.throw (Time_Error.Error err.payload.getMessage)
|
||||
|
||||
## Check two durations for equality.
|
||||
|
||||
Arguments:
|
||||
- that: The duration to compare against `self`.
|
||||
|
||||
> Examples
|
||||
Check if 60 seconds and 1 minute are equal.
|
||||
|
||||
import Standard.Base.Data.Time.Duration
|
||||
|
||||
example_eq = (Duration.new seconds=60).total_minutes == (Duration.new minutes=1).total_minutes
|
||||
== : Duration -> Boolean
|
||||
== self that =
|
||||
case that of
|
||||
_ : Duration -> self.equals_builtin that
|
||||
_ -> False
|
||||
|
||||
## Compares `self` to `that` to produce an ordering.
|
||||
|
||||
Arguments:
|
||||
|
@ -129,20 +129,6 @@ type Period
|
||||
DateTimeException -> Error.throw Time_Error.Error "Period subtraction failed"
|
||||
ArithmeticException -> Error.throw Illegal_Argument.Error "Arithmetic error"
|
||||
|
||||
## Check two periods for equality.
|
||||
|
||||
Note that two periods are equal if they have the exact same amount of
|
||||
years, months, and days. So `(Period.new days=30)` and
|
||||
`(Period.new months=1)` are not equal. Even `(Period.new years=1)` and
|
||||
`(Period.new months=12)` are not equal.
|
||||
|
||||
Arguments:
|
||||
- other_period: The period to compare against `self`.
|
||||
== : Period -> Boolean
|
||||
== self that =
|
||||
ensure_period that <|
|
||||
self.internal_period.equals that.internal_period
|
||||
|
||||
## Just throws `Incomparable_Values`, because periods cannot be
|
||||
compared without additional context.
|
||||
|
||||
|
@ -377,12 +377,3 @@ type Time_Of_Day
|
||||
sign = Time_Utils.compare_to_localtime self that
|
||||
Ordering.from_sign sign
|
||||
_ -> Error.throw (Type_Error.Error Time_Of_Day that "that")
|
||||
|
||||
## Compares two Time_Of_Day for equality.
|
||||
== : Date -> Boolean
|
||||
== self that = case that of
|
||||
Time_Of_Day -> Meta.is_same_object self Time_Of_Day
|
||||
_ : Time_Of_Day ->
|
||||
sign = Time_Utils.compare_to_localtime self that
|
||||
0 == sign
|
||||
_ -> False
|
||||
|
@ -169,10 +169,3 @@ type Time_Zone
|
||||
type_pair = ["type", "Time_Zone"]
|
||||
cons_pair = ["constructor", "new"]
|
||||
JS_Object.from_pairs [type_pair, cons_pair, ["id", self.zone_id]]
|
||||
|
||||
## Compares two Zones for equality.
|
||||
== : Time_Zone -> Boolean
|
||||
== self that =
|
||||
case that of
|
||||
_ : Time_Zone -> Time_Utils.equals_zone self that
|
||||
_ -> False
|
||||
|
@ -602,28 +602,6 @@ type Vector a
|
||||
"and " + remaining_count.to_text + " more elements"
|
||||
prefix.map .to_text . join ", " "[" " "+remaining_text+"]"
|
||||
|
||||
## Checks whether this vector is equal to `that`.
|
||||
|
||||
Arguments:
|
||||
- that: The vector to compare this vector against.
|
||||
|
||||
Two vectors are considered equal, when they have the same length and
|
||||
their items are pairwise equal.
|
||||
|
||||
> Example
|
||||
Compare two vectors for equality (this case is false).
|
||||
|
||||
[1, 2, 3] == [2, 3, 4]
|
||||
== : Vector -> Boolean
|
||||
== self that = case that of
|
||||
_ : Vector ->
|
||||
eq_at i = self.at i == that.at i
|
||||
if self.length == that.length then 0.up_to self.length . all eq_at else False
|
||||
_ : Array ->
|
||||
eq_at i = self.at i == that.at i
|
||||
if self.length == that.length then 0.up_to self.length . all eq_at else False
|
||||
_ -> False
|
||||
|
||||
## Concatenates two vectors, resulting in a new vector, containing all the
|
||||
elements of `self`, followed by all the elements of `that`.
|
||||
|
||||
|
@ -133,10 +133,3 @@ type Error
|
||||
1.is_error
|
||||
is_error : Boolean
|
||||
is_error self = True
|
||||
|
||||
## PRIVATE
|
||||
TODO this is a kludge until we have proper eigentypes and statics.
|
||||
Allows to check equality of the `Error` type with itself.
|
||||
== self that = if Meta.is_error self then self else
|
||||
if Meta.is_error that then that else
|
||||
Meta.is_same_object self that
|
||||
|
@ -208,14 +208,3 @@ type URI
|
||||
type_pair = ["type", "URI"]
|
||||
cons_pair = ["constructor", "parse"]
|
||||
JS_Object.from_pairs [type_pair, cons_pair, ["uri", self.to_text]]
|
||||
|
||||
## Check if this URI is equal to another URI.
|
||||
|
||||
> Example
|
||||
Check if two URIs are equal.
|
||||
|
||||
import Standard.Base.Network.URI.URI
|
||||
|
||||
example_eq = "https://example.com".to_uri == "http://example.org".to_uri
|
||||
== : URI -> Boolean
|
||||
== self that = self.internal_uri.equals that.internal_uri
|
||||
|
@ -427,17 +427,6 @@ type File
|
||||
normalize : File
|
||||
normalize self = @Builtin_Method "File.normalize"
|
||||
|
||||
## Checks if this file has the same `path` as `that`.
|
||||
|
||||
> Example
|
||||
Check if two files are equivalent.
|
||||
|
||||
import Standard.Examples
|
||||
|
||||
example_eq = Examples.csv == Examples.scratch_file
|
||||
== : File -> Boolean
|
||||
== self that = @Builtin_Method "File.=="
|
||||
|
||||
## Deletes the file.
|
||||
|
||||
If the file is a directory, it must be empty, otherwise a `Panic` will
|
||||
|
@ -32,7 +32,7 @@ check_integrity entity1 entity2 =
|
||||
- entity2: The entity to check against the first.
|
||||
check_connection : (Table | Column) -> (Table | Column) -> Boolean
|
||||
check_connection entity1 entity2 =
|
||||
entity1.connection == entity2.connection
|
||||
Meta.is_same_object entity1.connection entity2.connection
|
||||
|
||||
## PRIVATE
|
||||
|
||||
|
@ -157,7 +157,7 @@ using GitHub API, similarly as Enso releases.
|
||||
The primary purpose of the launcher is running various Enso components, namely
|
||||
the REPL, running a project, Enso scripts or the language server.
|
||||
|
||||
The launcher automatically infers which Enso version to used, based on the
|
||||
The launcher automatically infers which Enso version to use, based on the
|
||||
parameters and configuration:
|
||||
|
||||
- When running a project or the language server, the version specified in
|
||||
|
@ -0,0 +1,400 @@
|
||||
package org.enso.interpreter.bench.benchmarks.semantic;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.enso.polyglot.MethodNames.Module;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Value;
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.Setup;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
import org.openjdk.jmh.infra.BenchmarkParams;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
/**
|
||||
* Benchmarks for `Any.==` method. This benchmark takes two vectors as input, and compares each
|
||||
* pair of elements with `==`.
|
||||
*/
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@Fork(1)
|
||||
@Warmup(iterations = 3)
|
||||
@Measurement(iterations = 2)
|
||||
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||
@State(Scope.Benchmark)
|
||||
public class EqualsBenchmarks {
|
||||
|
||||
private static final int primitiveVectorSize = 4_000;
|
||||
private static final int stringsVectorSize = 3_000;
|
||||
/**
|
||||
* Maximum length of randomly generated strings.
|
||||
*/
|
||||
private static final int maxStringSize = 20;
|
||||
/**
|
||||
* Maximum depth of a tree (Node type). Every Node can have up to 5 children.
|
||||
*/
|
||||
private static final int maxTreeDepth = 4;
|
||||
/**
|
||||
* Size of the vector of trees (Node type).
|
||||
*/
|
||||
private static final int treeVectorSize = 500;
|
||||
private Value module;
|
||||
private Value benchFunc;
|
||||
|
||||
@Setup
|
||||
public void initializeBenchmark(BenchmarkParams params) throws Exception {
|
||||
var random = new Random(42);
|
||||
|
||||
var ctx = Context.newBuilder()
|
||||
.allowExperimentalOptions(true)
|
||||
.logHandler(new ByteArrayOutputStream())
|
||||
.allowIO(true)
|
||||
.allowAllAccess(true)
|
||||
.option(
|
||||
"enso.languageHomeOverride",
|
||||
Paths.get("../../distribution/component").toFile().getAbsolutePath()
|
||||
).build();
|
||||
|
||||
var benchmarkName = params.getBenchmark().replaceFirst(".*\\.", "");
|
||||
var codeBuilder = new StringBuilder("""
|
||||
import Standard.Base.Data.Range.Extensions
|
||||
|
||||
type Node
|
||||
C1 f1
|
||||
C2 f1 f2
|
||||
C3 f1 f2 f3
|
||||
C4 f1 f2 f3 f4
|
||||
C5 f1 f2 f3 f4 f5
|
||||
Nil
|
||||
Value value
|
||||
|
||||
eq_vec vec1 vec2 =
|
||||
(0.up_to vec1.length).map idx->
|
||||
(vec1.at idx) == (vec2.at idx)
|
||||
|
||||
eq x y = x == y
|
||||
"""
|
||||
);
|
||||
// Indexes where `True` is expected. Inside the generated vectors, on a predefined indexes,
|
||||
// we put "constant" values, such that when the elements at these indexes are compared,
|
||||
// `True` is returned.
|
||||
Set<Integer> trueExpectedAt;
|
||||
|
||||
switch (benchmarkName) {
|
||||
case "equalsPrimitives" -> {
|
||||
trueExpectedAt = Set.of(
|
||||
primitiveVectorSize / 2,
|
||||
primitiveVectorSize / 4,
|
||||
primitiveVectorSize / 8,
|
||||
primitiveVectorSize / 16,
|
||||
primitiveVectorSize / 32,
|
||||
primitiveVectorSize / 64
|
||||
);
|
||||
codeBuilder
|
||||
.append(
|
||||
generateVectorOfPrimitives(primitiveVectorSize, "vec1", 42, trueExpectedAt, random)
|
||||
)
|
||||
.append("\n")
|
||||
.append(
|
||||
generateVectorOfPrimitives(primitiveVectorSize, "vec2", 42, trueExpectedAt, random)
|
||||
)
|
||||
.append("\n");
|
||||
}
|
||||
case "equalsStrings" -> {
|
||||
trueExpectedAt = Set.of(
|
||||
treeVectorSize / 2,
|
||||
treeVectorSize / 4,
|
||||
treeVectorSize / 8
|
||||
);
|
||||
codeBuilder
|
||||
.append(
|
||||
generateVectorOfStrings(stringsVectorSize, "vec1", "AAA", trueExpectedAt, random)
|
||||
)
|
||||
.append("\n")
|
||||
.append(
|
||||
generateVectorOfStrings(stringsVectorSize, "vec2", "AAA", trueExpectedAt, random)
|
||||
)
|
||||
.append("\n");
|
||||
}
|
||||
case "equalsTrees" -> {
|
||||
trueExpectedAt = Set.of(
|
||||
treeVectorSize / 2,
|
||||
treeVectorSize / 4,
|
||||
treeVectorSize / 8,
|
||||
treeVectorSize / 16
|
||||
);
|
||||
codeBuilder
|
||||
.append(
|
||||
generateVectorOfTrees(treeVectorSize, "vec1", maxTreeDepth, createNilNode(), trueExpectedAt, random)
|
||||
)
|
||||
.append("\n")
|
||||
.append(
|
||||
generateVectorOfTrees(treeVectorSize, "vec2", maxTreeDepth, createNilNode(), trueExpectedAt, random)
|
||||
)
|
||||
.append("\n");
|
||||
}
|
||||
default ->
|
||||
throw new IllegalStateException("Unexpected benchmark: " + params.getBenchmark());
|
||||
}
|
||||
|
||||
codeBuilder.append("""
|
||||
bench x = eq_vec vec1 vec2
|
||||
""");
|
||||
|
||||
module = ctx.eval(
|
||||
SrcUtil.source(benchmarkName, codeBuilder.toString())
|
||||
);
|
||||
|
||||
benchFunc = module.invokeMember(Module.EVAL_EXPRESSION, "bench");
|
||||
|
||||
// Sanity check - elements in `trueExpectedAt` should be the same
|
||||
var eqFunc = module.invokeMember(Module.EVAL_EXPRESSION, "eq");
|
||||
var vec1 = module.invokeMember(Module.EVAL_EXPRESSION, "vec1");
|
||||
var vec2 = module.invokeMember(Module.EVAL_EXPRESSION, "vec2");
|
||||
for (Integer idx : trueExpectedAt) {
|
||||
var result = eqFunc.execute(vec1.getArrayElement(idx), vec2.getArrayElement(idx));
|
||||
if (!result.asBoolean()) {
|
||||
throw new AssertionError("Elements of input vectors at " + idx + " should be the same.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over {@link #primitiveVectorSize} long vector of random generated primitive values - integers,
|
||||
* doubles, and strings
|
||||
* @param blackHole
|
||||
*/
|
||||
@Benchmark
|
||||
public void equalsPrimitives(Blackhole blackHole) {
|
||||
performBenchmark(blackHole);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void equalsStrings(Blackhole blackhole) {
|
||||
performBenchmark(blackhole);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void equalsTrees(Blackhole blackhole) {
|
||||
performBenchmark(blackhole);
|
||||
}
|
||||
|
||||
private void performBenchmark(Blackhole blackhole) {
|
||||
Object res = benchFunc.execute(0);
|
||||
blackhole.consume(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates source code for a vector of primitive values. The vector will contain integers and
|
||||
* doubles. Count of elements of these different value types is equally distributed,
|
||||
* i.e., there is exact same amount of integers and doubles. Vector is
|
||||
* shuffled, so that there should not be a long consecutive range of values of just one type.
|
||||
* <p>
|
||||
* Generates code of form {@code vecName = [...]}
|
||||
*
|
||||
* @param totalSize Total size of the generated vector.
|
||||
* @param vecName Name of the generated vector.
|
||||
* @param identityElem A primitive element considered an identity with respect to `==` operator,
|
||||
* will be put in indexes denoted by {@code constantIdxs}
|
||||
* @param constantIdxs Indexes where {@code identityElem} will be put.
|
||||
* @param random Random number generator.
|
||||
* @return Source of the generated vector
|
||||
*/
|
||||
private static String generateVectorOfPrimitives(int totalSize, String vecName, Object identityElem, Collection<Integer> constantIdxs, Random random) {
|
||||
var partSize = totalSize / 2;
|
||||
List<Object> primitiveValues = new ArrayList<>();
|
||||
random.ints(partSize).forEach(primitiveValues::add);
|
||||
random.doubles(partSize).forEach(primitiveValues::add);
|
||||
Collections.shuffle(primitiveValues, random);
|
||||
for (Integer constantIdx : constantIdxs) {
|
||||
primitiveValues.set(constantIdx, identityElem);
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.append(vecName).append(" = [");
|
||||
for (Object primitiveValue : primitiveValues) {
|
||||
if (primitiveValue instanceof Double dbl) {
|
||||
sb.append(String.format("%f", dbl)).append(",");
|
||||
} else {
|
||||
sb.append(primitiveValue).append(",");
|
||||
}
|
||||
}
|
||||
// Replace last comma
|
||||
sb.setCharAt(sb.length() - 1, ']');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String generateVectorOfStrings(int size, String vecName, String identityElem, Collection<Integer> identityIdxs, Random random) {
|
||||
var sb = new StringBuilder();
|
||||
sb.append(vecName).append(" = [");
|
||||
for (int i = 0; i < size; i++) {
|
||||
var stringSize = random.nextInt(maxStringSize);
|
||||
var str = identityIdxs.contains(i) ? identityElem : randomString(stringSize, random);
|
||||
sb.append("\"").append(str).append("\"").append(",");
|
||||
}
|
||||
// Replace last comma
|
||||
sb.setCharAt(sb.length() - 1, ']');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static Node createNilNode() {
|
||||
return new NilNode(0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates source code for a vector of trees (Node type), i.e., generates an expression
|
||||
* {@code vecName = [...]}.
|
||||
*
|
||||
* @param size Total size of the generated vector.
|
||||
* @param vecName How the vector should be named.
|
||||
* @param maxDepth Maximum depth of the generated tree. Note that there is no lower bound, so
|
||||
* the generated tree can have depth 1.
|
||||
* @param identityNode A node that is considered an identity with respect to `==` operator.
|
||||
* This node will be put on every indes of {@code constantIdxs}.
|
||||
* @param constantIdxs Indexes in the vector where {@code identityNode} should be put.
|
||||
* @param random Random number generator.
|
||||
* @return Source code for the generated tree.
|
||||
*/
|
||||
private static String generateVectorOfTrees(int size, String vecName, int maxDepth, Node identityNode, Collection<Integer> constantIdxs, Random random) {
|
||||
var trees = new ArrayList<Node>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
trees.add(
|
||||
generateTree(null, 0, random, maxDepth)
|
||||
);
|
||||
}
|
||||
for (Integer constantIdx : constantIdxs) {
|
||||
trees.set(constantIdx, identityNode);
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.append(vecName).append(" = [");
|
||||
for (Node tree : trees) {
|
||||
sb.append(tree.createSource()).append(",");
|
||||
}
|
||||
sb.setCharAt(sb.length() - 1, ']');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static Node generateTree(Node parent, int currDepth, Random random, int maxDepth) {
|
||||
Node node;
|
||||
if (currDepth == maxDepth) {
|
||||
node = new NilNode(currDepth, parent);
|
||||
} else {
|
||||
if (random.nextBoolean() && currDepth > 0) {
|
||||
node = new ValueNode(
|
||||
currDepth,
|
||||
parent,
|
||||
random.nextInt()
|
||||
);
|
||||
} else {
|
||||
node = new NodeWithChildren(currDepth, parent);
|
||||
// childCount is between 1..5
|
||||
var childCount = Math.max(random.nextInt(5), 1);
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
generateTree(node, currDepth + 1, random, maxDepth);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parent instanceof NodeWithChildren parentWithChildren) {
|
||||
parentWithChildren.addChild(node);
|
||||
} else if (parent != null){
|
||||
throw new AssertionError("expected parent to be NodeWithChildren or null");
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
private static final String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQESRTUVWXYZ";
|
||||
private static String randomString(int size, Random random) {
|
||||
var sb = new StringBuilder(size);
|
||||
random
|
||||
.ints(size, 0, characters.length())
|
||||
.mapToObj(characters::charAt)
|
||||
.forEach(sb::append);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A simple hierarchy of Node classes that simplifies source code generation.
|
||||
*/
|
||||
abstract static class Node {
|
||||
final int depth;
|
||||
final Node parent;
|
||||
|
||||
Node(int depth, Node parent) {
|
||||
this.depth = depth;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
abstract String createSource();
|
||||
}
|
||||
|
||||
private static final class ValueNode extends Node {
|
||||
final int value;
|
||||
ValueNode(int depth, Node parent, int value) {
|
||||
super(depth, parent);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createSource() {
|
||||
return "(Node.Value " + value + ")";
|
||||
}
|
||||
}
|
||||
|
||||
private static final class NilNode extends Node {
|
||||
|
||||
NilNode(int depth, Node parent) {
|
||||
super(depth, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
String createSource() {
|
||||
return "Node.Nil";
|
||||
}
|
||||
}
|
||||
|
||||
private static final class NodeWithChildren extends Node {
|
||||
private List<Node> children = new ArrayList<>();
|
||||
|
||||
NodeWithChildren(int depth, Node parent) {
|
||||
super(depth, parent);
|
||||
}
|
||||
|
||||
void addChild(Node node) {
|
||||
children.add(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createSource() {
|
||||
String ctor = switch(children.size()) {
|
||||
case 1 -> "(Node.C1 ";
|
||||
case 2 -> "(Node.C2 ";
|
||||
case 3 -> "(Node.C3 ";
|
||||
case 4 -> "(Node.C4 ";
|
||||
case 5 -> "(Node.C5 ";
|
||||
default -> throw new AssertionError("Unexpected number of children: " + children.size());
|
||||
};
|
||||
var sb = new StringBuilder();
|
||||
sb.append(ctor);
|
||||
for (Node child : children) {
|
||||
sb.append(child.createSource()).append(" ");
|
||||
}
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package org.enso.interpreter.node.expression.builtin.bool;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Fallback;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
|
||||
@BuiltinMethod(type = "Boolean", name = "==", description = "Computes the equality of two booleans")
|
||||
public abstract class EqualsNode extends Node {
|
||||
abstract boolean execute(Object self, Object that);
|
||||
|
||||
static EqualsNode build() {
|
||||
return EqualsNodeGen.create();
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doBoolean(boolean self, boolean that) {
|
||||
return self == that;
|
||||
}
|
||||
|
||||
@Fallback
|
||||
boolean doOther(Object self, Object that) {
|
||||
return self == that;
|
||||
}
|
||||
}
|
@ -0,0 +1,557 @@
|
||||
package org.enso.interpreter.node.expression.builtin.meta;
|
||||
|
||||
import com.ibm.icu.text.Normalizer;
|
||||
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.GenerateUncached;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.interop.ArityException;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
|
||||
import com.oracle.truffle.api.interop.UnknownIdentifierException;
|
||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
import com.oracle.truffle.api.interop.UnsupportedTypeException;
|
||||
import com.oracle.truffle.api.library.CachedLibrary;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import com.oracle.truffle.api.profiles.ConditionProfile;
|
||||
import com.oracle.truffle.api.profiles.LoopConditionProfile;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import org.enso.interpreter.dsl.AcceptsError;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.node.callable.ExecuteCallNode;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsAnyNodeGen.InvokeEqualsNodeGen;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
|
||||
import org.enso.interpreter.runtime.callable.atom.Atom;
|
||||
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.data.Type;
|
||||
import org.enso.interpreter.runtime.error.WarningsLibrary;
|
||||
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
import org.enso.interpreter.runtime.state.State;
|
||||
import org.enso.polyglot.MethodNames;
|
||||
|
||||
@BuiltinMethod(
|
||||
type = "Any",
|
||||
name = "==",
|
||||
description = "Implementation of Any.=="
|
||||
)
|
||||
@GenerateUncached
|
||||
public abstract class EqualsAnyNode extends Node {
|
||||
|
||||
protected static String EQUALS_MEMBER_NAME = MethodNames.Function.EQUALS;
|
||||
|
||||
public static EqualsAnyNode build() {
|
||||
return EqualsAnyNodeGen.create();
|
||||
}
|
||||
public abstract boolean execute(@AcceptsError Object self, @AcceptsError Object right);
|
||||
|
||||
/** Primitive values **/
|
||||
|
||||
|
||||
@Specialization
|
||||
boolean equalsBoolean(boolean self, boolean other) {
|
||||
return self == other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsLong(long self, long other) {
|
||||
return self == other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsDouble(double self, double other) {
|
||||
return self == other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsLongDouble(long self, double other) {
|
||||
return (double) self == other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsDoubleLong(double self, long other) {
|
||||
return self == (double) other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsIntLong(int self, long other) {
|
||||
return (long) self == other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsLongInt(long self, int other) {
|
||||
return self == (long) other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsIntDouble(int self, double other) {
|
||||
return (double) self == other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsDoubleInt(double self, int other) {
|
||||
return self == (double) other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
boolean equalsBigInt(EnsoBigInteger self, EnsoBigInteger otherBigInt) {
|
||||
return self.equals(otherBigInt);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
boolean equalsBitIntDouble(EnsoBigInteger self, double other) {
|
||||
return self.doubleValue() == other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@TruffleBoundary
|
||||
boolean equalsDoubleBigInt(double self, EnsoBigInteger other) {
|
||||
return self == other.doubleValue();
|
||||
}
|
||||
|
||||
/** Enso specific types **/
|
||||
|
||||
@Specialization
|
||||
boolean equalsUnresolvedSymbols(UnresolvedSymbol self, UnresolvedSymbol otherSymbol,
|
||||
@Cached EqualsAnyNode equalsNode) {
|
||||
return self.getName().equals(otherSymbol.getName())
|
||||
&& equalsNode.execute(self.getScope(), otherSymbol.getScope());
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsUnresolvedConversion(UnresolvedConversion selfConversion, UnresolvedConversion otherConversion,
|
||||
@Cached EqualsAnyNode equalsNode) {
|
||||
return equalsNode.execute(selfConversion.getScope(), otherConversion.getScope());
|
||||
}
|
||||
|
||||
/**
|
||||
* If one of the objects has warnings attached, just treat it as an object without any
|
||||
* warnings.
|
||||
*/
|
||||
@Specialization(guards = {
|
||||
"selfWarnLib.hasWarnings(selfWithWarnings) || otherWarnLib.hasWarnings(otherWithWarnings)"
|
||||
}, limit = "3")
|
||||
boolean equalsWithWarnings(Object selfWithWarnings, Object otherWithWarnings,
|
||||
@CachedLibrary("selfWithWarnings") WarningsLibrary selfWarnLib,
|
||||
@CachedLibrary("otherWithWarnings") WarningsLibrary otherWarnLib,
|
||||
@Cached EqualsAnyNode equalsNode
|
||||
) {
|
||||
try {
|
||||
Object self =
|
||||
selfWarnLib.hasWarnings(selfWithWarnings) ? selfWarnLib.removeWarnings(selfWithWarnings)
|
||||
: selfWithWarnings;
|
||||
Object other =
|
||||
otherWarnLib.hasWarnings(otherWithWarnings) ? otherWarnLib.removeWarnings(otherWithWarnings)
|
||||
: otherWithWarnings;
|
||||
return equalsNode.execute(self, other);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Interop libraries **/
|
||||
|
||||
@Specialization(guards = {
|
||||
"selfInterop.isNull(selfNull)",
|
||||
"otherInterop.isNull(otherNull)"
|
||||
}, limit = "3")
|
||||
boolean equalsNull(
|
||||
Object selfNull, Object otherNull,
|
||||
@CachedLibrary("selfNull") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherNull") InteropLibrary otherInterop
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Specialization(guards = {
|
||||
"selfInterop.isBoolean(selfBoolean)",
|
||||
"otherInterop.isBoolean(otherBoolean)"
|
||||
}, limit = "3")
|
||||
boolean equalsBooleanInterop(
|
||||
Object selfBoolean,
|
||||
Object otherBoolean,
|
||||
@CachedLibrary("selfBoolean") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherBoolean") InteropLibrary otherInterop
|
||||
) {
|
||||
try {
|
||||
return selfInterop.asBoolean(selfBoolean) == otherInterop.asBoolean(otherBoolean);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(guards = {
|
||||
"!selfInterop.isDate(selfTimeZone)",
|
||||
"!selfInterop.isTime(selfTimeZone)",
|
||||
"selfInterop.isTimeZone(selfTimeZone)",
|
||||
"!otherInterop.isDate(otherTimeZone)",
|
||||
"!otherInterop.isTime(otherTimeZone)",
|
||||
"otherInterop.isTimeZone(otherTimeZone)"
|
||||
}, limit = "3")
|
||||
boolean equalsTimeZones(Object selfTimeZone, Object otherTimeZone,
|
||||
@CachedLibrary("selfTimeZone") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherTimeZone") InteropLibrary otherInterop) {
|
||||
try {
|
||||
return selfInterop.asTimeZone(selfTimeZone).equals(
|
||||
otherInterop.asTimeZone(otherTimeZone)
|
||||
);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
@Specialization(guards = {
|
||||
"selfInterop.isDate(selfZonedDateTime)",
|
||||
"selfInterop.isTime(selfZonedDateTime)",
|
||||
"selfInterop.isTimeZone(selfZonedDateTime)",
|
||||
"otherInterop.isDate(otherZonedDateTime)",
|
||||
"otherInterop.isTime(otherZonedDateTime)",
|
||||
"otherInterop.isTimeZone(otherZonedDateTime)"
|
||||
}, limit = "3")
|
||||
boolean equalsZonedDateTimes(Object selfZonedDateTime, Object otherZonedDateTime,
|
||||
@CachedLibrary("selfZonedDateTime") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherZonedDateTime") InteropLibrary otherInterop) {
|
||||
try {
|
||||
var self = ZonedDateTime.of(
|
||||
selfInterop.asDate(selfZonedDateTime),
|
||||
selfInterop.asTime(selfZonedDateTime),
|
||||
selfInterop.asTimeZone(selfZonedDateTime)
|
||||
);
|
||||
var other = ZonedDateTime.of(
|
||||
otherInterop.asDate(otherZonedDateTime),
|
||||
otherInterop.asTime(otherZonedDateTime),
|
||||
otherInterop.asTimeZone(otherZonedDateTime)
|
||||
);
|
||||
return self.isEqual(other);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(guards = {
|
||||
"selfInterop.isDate(selfDateTime)",
|
||||
"selfInterop.isTime(selfDateTime)",
|
||||
"!selfInterop.isTimeZone(selfDateTime)",
|
||||
"otherInterop.isDate(otherDateTime)",
|
||||
"otherInterop.isTime(otherDateTime)",
|
||||
"!otherInterop.isTimeZone(otherDateTime)"
|
||||
}, limit = "3")
|
||||
boolean equalsDateTimes(Object selfDateTime, Object otherDateTime,
|
||||
@CachedLibrary("selfDateTime") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherDateTime") InteropLibrary otherInterop) {
|
||||
try {
|
||||
var self = LocalDateTime.of(
|
||||
selfInterop.asDate(selfDateTime),
|
||||
selfInterop.asTime(selfDateTime)
|
||||
);
|
||||
var other = LocalDateTime.of(
|
||||
otherInterop.asDate(otherDateTime),
|
||||
otherInterop.asTime(otherDateTime)
|
||||
);
|
||||
return self.isEqual(other);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(guards = {
|
||||
"selfInterop.isDate(selfDate)",
|
||||
"!selfInterop.isTime(selfDate)",
|
||||
"!selfInterop.isTimeZone(selfDate)",
|
||||
"otherInterop.isDate(otherDate)",
|
||||
"!otherInterop.isTime(otherDate)",
|
||||
"!otherInterop.isTimeZone(otherDate)"
|
||||
}, limit = "3")
|
||||
boolean equalsDates(Object selfDate, Object otherDate,
|
||||
@CachedLibrary("selfDate") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherDate") InteropLibrary otherInterop) {
|
||||
try {
|
||||
return selfInterop.asDate(selfDate).isEqual(
|
||||
otherInterop.asDate(otherDate)
|
||||
);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(guards = {
|
||||
"!selfInterop.isDate(selfTime)",
|
||||
"selfInterop.isTime(selfTime)",
|
||||
"!selfInterop.isTimeZone(selfTime)",
|
||||
"!otherInterop.isDate(otherTime)",
|
||||
"otherInterop.isTime(otherTime)",
|
||||
"!otherInterop.isTimeZone(otherTime)"
|
||||
}, limit = "3")
|
||||
boolean equalsTimes(Object selfTime, Object otherTime,
|
||||
@CachedLibrary("selfTime") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherTime") InteropLibrary otherInterop) {
|
||||
try {
|
||||
return selfInterop.asTime(selfTime).equals(
|
||||
otherInterop.asTime(otherTime)
|
||||
);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(guards = {
|
||||
"selfInterop.isDuration(selfDuration)",
|
||||
"otherInterop.isDuration(otherDuration)"
|
||||
}, limit = "3")
|
||||
boolean equalsDuration(Object selfDuration, Object otherDuration,
|
||||
@CachedLibrary("selfDuration") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherDuration") InteropLibrary otherInterop) {
|
||||
try {
|
||||
return selfInterop.asDuration(selfDuration).equals(
|
||||
otherInterop.asDuration(otherDuration)
|
||||
);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares interop strings according to the lexicographical order, handling Unicode
|
||||
* normalization. See {@code Text_Utils.compare_to}.
|
||||
*/
|
||||
@TruffleBoundary
|
||||
@Specialization(guards = {
|
||||
"selfInterop.isString(selfString)",
|
||||
"otherInterop.isString(otherString)"
|
||||
}, limit = "3")
|
||||
boolean equalsStrings(Object selfString, Object otherString,
|
||||
@CachedLibrary("selfString") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherString") InteropLibrary otherInterop) {
|
||||
String selfJavaString;
|
||||
String otherJavaString;
|
||||
try {
|
||||
selfJavaString = selfInterop.asString(selfString);
|
||||
otherJavaString = otherInterop.asString(otherString);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return Normalizer.compare(
|
||||
selfJavaString,
|
||||
otherJavaString,
|
||||
Normalizer.FOLD_CASE_DEFAULT
|
||||
) == 0;
|
||||
}
|
||||
|
||||
@Specialization(guards = {
|
||||
"selfInterop.hasArrayElements(selfArray)",
|
||||
"otherInterop.hasArrayElements(otherArray)"
|
||||
}, limit = "3")
|
||||
boolean equalsArrays(Object selfArray, Object otherArray,
|
||||
@CachedLibrary("selfArray") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherArray") InteropLibrary otherInterop,
|
||||
@Cached EqualsAnyNode equalsNode
|
||||
) {
|
||||
try {
|
||||
long selfSize = selfInterop.getArraySize(selfArray);
|
||||
if (selfSize != otherInterop.getArraySize(otherArray)) {
|
||||
return false;
|
||||
}
|
||||
for (long i = 0; i < selfSize; i++) {
|
||||
Object selfElem = selfInterop.readArrayElement(selfArray, i);
|
||||
Object otherElem = otherInterop.readArrayElement(otherArray, i);
|
||||
if (!equalsNode.execute(selfElem, otherElem)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (UnsupportedMessageException | InvalidArrayIndexException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Equals for Atoms and AtomConstructors */
|
||||
|
||||
@Specialization
|
||||
boolean equalsAtomConstructors(AtomConstructor selfConstructor, AtomConstructor otherConstructor) {
|
||||
return selfConstructor == otherConstructor;
|
||||
}
|
||||
|
||||
/**
|
||||
* How many {@link EqualsAnyNode} should be created for fields in specialization for atoms.
|
||||
*/
|
||||
static final int equalsNodeCountForFields = 10;
|
||||
|
||||
static EqualsAnyNode[] createEqualsNodes(int size) {
|
||||
EqualsAnyNode[] nodes = new EqualsAnyNode[size];
|
||||
Arrays.fill(nodes, EqualsAnyNode.build());
|
||||
return nodes;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsAtoms(Atom self, Atom other,
|
||||
@Cached LoopConditionProfile loopProfile,
|
||||
@Cached(value = "createEqualsNodes(equalsNodeCountForFields)", allowUncached = true) EqualsAnyNode[] fieldEqualsNodes,
|
||||
@Cached InvokeEqualsNode atomInvokeEqualsNode,
|
||||
@Cached ConditionProfile enoughEqualNodesForFieldsProfile,
|
||||
@Cached ConditionProfile constructorsNotEqualProfile) {
|
||||
if (atomOverridesEquals(self)) {
|
||||
return atomInvokeEqualsNode.execute(self, other);
|
||||
}
|
||||
|
||||
if (constructorsNotEqualProfile.profile(
|
||||
self.getConstructor() != other.getConstructor()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
assert self.getFields().length == other.getFields().length;
|
||||
|
||||
int fieldsSize = self.getFields().length;
|
||||
if (enoughEqualNodesForFieldsProfile.profile(fieldsSize <= equalsNodeCountForFields)) {
|
||||
loopProfile.profileCounted(fieldsSize);
|
||||
for (int i = 0; loopProfile.inject(i < fieldsSize); i++) {
|
||||
if (!fieldEqualsNodes[i].execute(
|
||||
self.getFields()[i],
|
||||
other.getFields()[i]
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return equalsAtomsFieldsUncached(self.getFields(), other.getFields());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
private static boolean equalsAtomsFieldsUncached(Object[] selfFields, Object[] otherFields) {
|
||||
assert selfFields.length == otherFields.length;
|
||||
for (int i = 0; i < selfFields.length; i++) {
|
||||
boolean areFieldsSame;
|
||||
if (selfFields[i] instanceof Atom selfFieldAtom
|
||||
&& otherFields[i] instanceof Atom otherFieldAtom
|
||||
&& atomOverridesEquals(selfFieldAtom)) {
|
||||
areFieldsSame = InvokeEqualsNode.getUncached().execute(selfFieldAtom, otherFieldAtom);
|
||||
} else {
|
||||
areFieldsSame = EqualsAnyNodeGen.getUncached().execute(selfFields[i], otherFields[i]);
|
||||
}
|
||||
if (!areFieldsSame) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper node for invoking `==` method on atoms, that override this method.
|
||||
*/
|
||||
@GenerateUncached
|
||||
static abstract class InvokeEqualsNode extends Node {
|
||||
static InvokeEqualsNode getUncached() {
|
||||
return InvokeEqualsNodeGen.getUncached();
|
||||
}
|
||||
|
||||
static InvokeEqualsNode build() {
|
||||
return InvokeEqualsNodeGen.create();
|
||||
}
|
||||
|
||||
abstract boolean execute(Atom selfAtom, Atom otherAtom);
|
||||
|
||||
@Specialization(guards = "cachedSelfAtomCtor == selfAtom.getConstructor()")
|
||||
boolean invokeEqualsCachedAtomCtor(Atom selfAtom, Atom otherAtom,
|
||||
@Cached("selfAtom.getConstructor()") AtomConstructor cachedSelfAtomCtor,
|
||||
@Cached("getEqualsMethod(cachedSelfAtomCtor)") Function equalsMethod,
|
||||
@Cached ExecuteCallNode executeCallNode,
|
||||
@CachedLibrary(limit = "3") InteropLibrary interop) {
|
||||
assert atomOverridesEquals(selfAtom);
|
||||
Object ret = executeCallNode.executeCall(
|
||||
equalsMethod,
|
||||
null,
|
||||
State.create(EnsoContext.get(this)),
|
||||
new Object[]{selfAtom, otherAtom}
|
||||
);
|
||||
try {
|
||||
return interop.asBoolean(ret);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
@Specialization(replaces = "invokeEqualsCachedAtomCtor")
|
||||
boolean invokeEqualsUncached(Atom selfAtom, Atom otherAtom,
|
||||
@Cached ExecuteCallNode executeCallNode) {
|
||||
Function equalsMethod = getEqualsMethod(selfAtom.getConstructor());
|
||||
Object ret = executeCallNode.executeCall(
|
||||
equalsMethod,
|
||||
null,
|
||||
State.create(EnsoContext.get(this)),
|
||||
new Object[]{selfAtom, otherAtom}
|
||||
);
|
||||
assert InteropLibrary.getUncached().isBoolean(ret);
|
||||
try {
|
||||
return InteropLibrary.getUncached().asBoolean(ret);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
static Function getEqualsMethod(AtomConstructor atomConstructor) {
|
||||
Type atomType = atomConstructor.getType();
|
||||
Function equalsFunction = atomConstructor
|
||||
.getDefinitionScope()
|
||||
.getMethods()
|
||||
.get(atomType)
|
||||
.get("==");
|
||||
assert equalsFunction != null;
|
||||
return equalsFunction;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given atom overrides `==` operator.
|
||||
*/
|
||||
@TruffleBoundary
|
||||
private static boolean atomOverridesEquals(Atom atom) {
|
||||
var atomType = atom.getConstructor().getType();
|
||||
Map<String, Function> methodsOnType = atom
|
||||
.getConstructor()
|
||||
.getDefinitionScope()
|
||||
.getMethods()
|
||||
.get(atomType);
|
||||
if (methodsOnType != null) {
|
||||
return methodsOnType.containsKey("==");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Fallback
|
||||
@TruffleBoundary
|
||||
boolean equalsGeneric(Object left, Object right,
|
||||
@CachedLibrary(limit = "5") InteropLibrary interop) {
|
||||
EnsoContext ctx = EnsoContext.get(interop);
|
||||
if (isHostObject(ctx, left) && isHostObject(ctx, right)) {
|
||||
try {
|
||||
return interop.asBoolean(
|
||||
interop.invokeMember(left, "equals", right)
|
||||
);
|
||||
} catch (UnsupportedMessageException | ArityException | UnknownIdentifierException |
|
||||
UnsupportedTypeException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
} else {
|
||||
return left == right
|
||||
|| left.equals(right)
|
||||
|| interop.isIdentical(left, right, interop);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isHostObject(EnsoContext context, Object object) {
|
||||
return context.getEnvironment().isHostObject(object);
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package org.enso.interpreter.node.expression.builtin.number.bigInteger;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Fallback;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps;
|
||||
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
|
||||
@BuiltinMethod(type = "Big_Integer", name = "==", description = "Big integer equality.")
|
||||
public abstract class EqualsNode extends Node {
|
||||
|
||||
abstract boolean execute(Object self, Object that);
|
||||
|
||||
static EqualsNode build() {
|
||||
return EqualsNodeGen.create();
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doBigInt(EnsoBigInteger self, EnsoBigInteger that) {
|
||||
return BigIntegerOps.equals(self.getValue(), that.getValue());
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doDouble(EnsoBigInteger self, double that) {
|
||||
return BigIntegerOps.toDouble(self.getValue()) == that;
|
||||
}
|
||||
|
||||
@Fallback
|
||||
boolean doOther(Object self, Object that) {
|
||||
return self == that;
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package org.enso.interpreter.node.expression.builtin.number.decimal;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Fallback;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps;
|
||||
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
|
||||
@BuiltinMethod(type = "Decimal", name = "==", description = "Equality on numbers.")
|
||||
public abstract class EqualsNode extends Node {
|
||||
abstract boolean execute(Object self, Object that);
|
||||
|
||||
static EqualsNode build() {
|
||||
return EqualsNodeGen.create();
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doDouble(double self, double that) {
|
||||
return self == that;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doLong(double self, long that) {
|
||||
return self == (double) that;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doBigInteger(double self, EnsoBigInteger that) {
|
||||
return self == BigIntegerOps.toDouble(that.getValue());
|
||||
}
|
||||
|
||||
@Fallback
|
||||
boolean doOther(Object self, Object that) {
|
||||
return self == that;
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package org.enso.interpreter.node.expression.builtin.number.smallInteger;
|
||||
|
||||
import com.oracle.truffle.api.dsl.Fallback;
|
||||
import com.oracle.truffle.api.dsl.Specialization;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
|
||||
@BuiltinMethod(type = "Small_Integer", name = "==", description = "Equality on numbers.")
|
||||
public abstract class EqualsNode extends Node {
|
||||
|
||||
abstract boolean execute(Object self, Object that);
|
||||
|
||||
static EqualsNode build() {
|
||||
return EqualsNodeGen.create();
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doLong(long self, long that) {
|
||||
return self == that;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean doDouble(long self, double that) {
|
||||
return (double) self == that;
|
||||
}
|
||||
|
||||
@Fallback
|
||||
boolean doOther(Object self, Object that) {
|
||||
return self == that;
|
||||
}
|
||||
}
|
@ -120,7 +120,7 @@ public final class EnsoDate implements TruffleObject {
|
||||
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
@ExportMessage
|
||||
public final Object toDisplayString(boolean allowSideEffects) {
|
||||
public Object toDisplayString(boolean allowSideEffects) {
|
||||
return DateTimeFormatter.ISO_LOCAL_DATE.format(date);
|
||||
}
|
||||
}
|
||||
|
@ -180,14 +180,6 @@ public final class EnsoDuration implements TruffleObject {
|
||||
return duration.compareTo(interop.asDuration(durationObject));
|
||||
}
|
||||
|
||||
@Builtin.Method(name = "equals_builtin")
|
||||
@Builtin.Specialize
|
||||
@Builtin.WrapException(from = UnsupportedMessageException.class)
|
||||
public boolean equalsDuration(Object durationObject, InteropLibrary interop)
|
||||
throws UnsupportedMessageException {
|
||||
return duration.equals(interop.asDuration(durationObject));
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
public boolean isDuration() {
|
||||
return true;
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.enso.interpreter.runtime.data;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.TruffleFile;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.TruffleObject;
|
||||
@ -180,13 +181,14 @@ public final class EnsoFile implements TruffleObject {
|
||||
return this.truffleFile.getName();
|
||||
}
|
||||
|
||||
@Builtin.Method(name = "==")
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
public boolean isEqual(EnsoFile that) {
|
||||
// It seems that fsContext is not equal in files coming from stacktraces.
|
||||
// Once a solution to that is found replace it with a simple
|
||||
// return this.truffleFile.equals(that.truffleFile);
|
||||
return this.getPath().equals(that.getPath());
|
||||
@TruffleBoundary
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof EnsoFile otherFile) {
|
||||
return truffleFile.getPath().equals(otherFile.truffleFile.getPath());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Builtin.Method
|
||||
|
@ -143,7 +143,7 @@ public final class EnsoTimeOfDay implements TruffleObject {
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
final boolean isDate() {
|
||||
boolean isDate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -103,12 +103,12 @@ public final class Vector implements TruffleObject {
|
||||
* @return {@code true}
|
||||
*/
|
||||
@ExportMessage
|
||||
public boolean hasArrayElements() {
|
||||
boolean hasArrayElements() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
public long getArraySize(@CachedLibrary(limit = "3") InteropLibrary interop)
|
||||
long getArraySize(@CachedLibrary(limit = "3") InteropLibrary interop)
|
||||
throws UnsupportedMessageException {
|
||||
return interop.getArraySize(storage);
|
||||
}
|
||||
@ -133,7 +133,6 @@ public final class Vector implements TruffleObject {
|
||||
if (warnings.hasWarnings(v)) {
|
||||
v = warnings.removeWarnings(v);
|
||||
}
|
||||
;
|
||||
return new WithWarnings(toEnso.execute(v), extracted);
|
||||
}
|
||||
return toEnso.execute(v);
|
||||
|
@ -156,4 +156,13 @@ public final class EnsoBigInteger extends Number implements TruffleObject {
|
||||
public double doubleValue() {
|
||||
return value.doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof EnsoBigInteger otherBigInt) {
|
||||
return value.equals(otherBigInt.value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -728,7 +728,6 @@ object BindingsMap {
|
||||
* @param name the name of the constructor.
|
||||
* @param arity the number of fields in the constructor.
|
||||
* @param allFieldsDefaulted whether all fields provide a default value.
|
||||
* @param builtinType true if constructor is annotated with @Builtin_Type, false otherwise.
|
||||
*/
|
||||
case class Cons(name: String, arity: Int, allFieldsDefaulted: Boolean)
|
||||
|
||||
@ -736,6 +735,7 @@ object BindingsMap {
|
||||
*
|
||||
* @param name the type name
|
||||
* @param members the member names
|
||||
* @param builtinType true if constructor is annotated with @Builtin_Type, false otherwise.
|
||||
*/
|
||||
case class Type(
|
||||
override val name: String,
|
||||
|
@ -34,6 +34,10 @@ spec =
|
||||
arr = make_enso_array [1, 2, 3]
|
||||
arr.method . should_equal 0
|
||||
|
||||
Test.specify "should handle ==" <|
|
||||
(make_enso_array [1,2,3]).should_equal (make_enso_array [1,2,3])
|
||||
(make_enso_array [1]).should_not_equal (make_enso_array [2])
|
||||
|
||||
Test.specify "should propagate dataflow errors" <|
|
||||
err = Error.throw (Illegal_State.Error "Foo")
|
||||
res = Array.new err
|
||||
|
@ -23,6 +23,13 @@ spec =
|
||||
True.compare_to False . should_equal Ordering.Greater
|
||||
False.compare_to True . should_equal Ordering.Less
|
||||
|
||||
Test.specify "should allow == operator" <|
|
||||
True.should_equal True
|
||||
False.should_equal False
|
||||
True.should_not_equal False
|
||||
False.should_not_equal True
|
||||
(1 == 1).should_equal True
|
||||
|
||||
Test.specify "should allow for extending Bools in a local module" <|
|
||||
test = 1 == 2
|
||||
test.method . should_equal test
|
||||
|
@ -33,6 +33,12 @@ spec =
|
||||
d3 . should_equal d4
|
||||
d4 . should_equal d5
|
||||
|
||||
Test.specify "should provide equality operator" <|
|
||||
(Date_Time.new 2022 zone=(Time_Zone.parse "CET")).should_not_equal (Date_Time.new 2022 zone=(Time_Zone.parse "UTC"))
|
||||
(Date_Time.new 2022 zone=(Time_Zone.parse "CET")).should_equal (Date_Time.new 2022 zone=(Time_Zone.parse "CET"))
|
||||
(Date_Time.new 2022 12 12).should_equal (Date_Time.new 2022 12 12)
|
||||
(Date_Time.new 2022 12 12).should_not_equal (Date_Time.new 1996)
|
||||
|
||||
spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=False =
|
||||
Test.group name <|
|
||||
|
||||
|
@ -12,6 +12,9 @@ spec =
|
||||
Test.group "Zone" <|
|
||||
Test.specify "should get system zone id" <|
|
||||
Time_Zone.system
|
||||
Test.specify "Different time zones should not equal" <|
|
||||
(Time_Zone.parse "UTC").should_not_equal (Time_Zone.parse "CET")
|
||||
(Time_Zone.parse "UTC").should_equal (Time_Zone.parse "UTC")
|
||||
Test.specify "should parse UTC zone" <|
|
||||
zone = "UTC"
|
||||
id = Time_Zone.parse zone
|
||||
|
@ -12,6 +12,7 @@ import project.Semantic.Import_Loop.Spec as Import_Loop_Spec
|
||||
import project.Semantic.Meta_Spec
|
||||
import project.Semantic.Meta_Location_Spec
|
||||
import project.Semantic.Names_Spec
|
||||
import project.Semantic.Equals_Spec
|
||||
import project.Semantic.Runtime_Spec
|
||||
import project.Semantic.Self_Type_Spec
|
||||
import project.Semantic.Warnings_Spec
|
||||
@ -108,6 +109,7 @@ main = Test_Suite.run_main <|
|
||||
Meta_Spec.spec
|
||||
Meta_Location_Spec.spec
|
||||
Names_Spec.spec
|
||||
Equals_Spec.spec
|
||||
Noise_Generator_Spec.spec
|
||||
Noise_Spec.spec
|
||||
Numbers_Spec.spec
|
||||
|
@ -31,4 +31,8 @@ spec =
|
||||
Test.specify "should return Syntax_Error when parsing invalid URI" <|
|
||||
URI.parse "a b c" . should_fail_with Syntax_Error.Error
|
||||
|
||||
Test.specify "should compare two URIs for equality" <|
|
||||
(URI.parse "http://google.com").should_equal (URI.parse "http://google.com")
|
||||
(URI.parse "http://google.com").should_not_equal (URI.parse "http://amazon.com")
|
||||
|
||||
main = Test_Suite.run_main spec
|
||||
|
186
test/Tests/src/Semantic/Equals_Spec.enso
Normal file
186
test/Tests/src/Semantic/Equals_Spec.enso
Normal file
@ -0,0 +1,186 @@
|
||||
from Standard.Base import all
|
||||
|
||||
from Standard.Test import Test, Test_Suite
|
||||
import Standard.Test.Extensions
|
||||
|
||||
polyglot java import java.util.HashMap
|
||||
|
||||
type CustomEqType
|
||||
C1 f1
|
||||
C2 f1 f2
|
||||
|
||||
sum self = case self of
|
||||
CustomEqType.C1 f1 -> f1
|
||||
CustomEqType.C2 f1 f2 -> f1 + f2
|
||||
|
||||
== self other = self.sum == other.sum
|
||||
|
||||
type Child
|
||||
Value number
|
||||
|
||||
== : Any -> Boolean
|
||||
== self other = case other of
|
||||
_ : Child -> (self.number % 100) == (other.number % 100)
|
||||
_ -> False
|
||||
|
||||
type Parent
|
||||
Value child
|
||||
|
||||
== : Any -> Boolean
|
||||
== self other = case other of
|
||||
_ : Parent -> self.child == other.child
|
||||
_ -> False
|
||||
|
||||
type GrandParent
|
||||
Value parent
|
||||
|
||||
== : Any -> Boolean
|
||||
== self other = case other of
|
||||
_ : GrandParent -> self.parent == other.parent
|
||||
_ -> False
|
||||
|
||||
type ManyFieldType
|
||||
Value f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13 f14 f15
|
||||
|
||||
type FourFieldType
|
||||
Value f1 f2 f3 f4
|
||||
|
||||
type Point
|
||||
Value x y
|
||||
|
||||
type Rect
|
||||
Value (a:Point) (b:Point)
|
||||
|
||||
type Node
|
||||
C1 f1
|
||||
C2 f1 f2
|
||||
C3 f1 f2 f3
|
||||
C4 f1 f2 f3 f4
|
||||
C5 f1 f2 f3 f4 f5
|
||||
Nil
|
||||
Value value
|
||||
|
||||
## Deep copy the tree to ensure that we cannot do Meta.is_same_object shortcut.
|
||||
deep_copy : Node
|
||||
deep_copy self =
|
||||
case self of
|
||||
Node.Nil -> Node.Nil
|
||||
Node.Value value -> Node.Value value
|
||||
Node.C1 f1 -> Node.C1 (f1.deep_copy)
|
||||
Node.C2 f1 f2 -> Node.C2 (f1.deep_copy) (f2.deep_copy)
|
||||
Node.C3 f1 f2 f3 -> Node.C3 (f1.deep_copy) (f2.deep_copy) (f3.deep_copy)
|
||||
Node.C4 f1 f2 f3 f4 -> Node.C4 (f1.deep_copy) (f2.deep_copy) (f3.deep_copy) (f4.deep_copy)
|
||||
Node.C5 f1 f2 f3 f4 f5 -> Node.C5 (f1.deep_copy) (f2.deep_copy) (f3.deep_copy) (f4.deep_copy) (f5.deep_copy)
|
||||
_ -> Error.throw ("Unexpected type " + self.to_text)
|
||||
|
||||
|
||||
create_random_tree : Integer -> Java_Random -> Integer
|
||||
create_random_tree max_depth rnd cur_depth=0 =
|
||||
if cur_depth == max_depth then Node.Nil else
|
||||
next_depth = cur_depth + 1
|
||||
children_count = rnd.nextInt 6
|
||||
children = (0.up_to children_count).map _-> @Tail_Call create_random_tree max_depth rnd next_depth
|
||||
case children_count of
|
||||
0 -> Node.Value 42
|
||||
1 -> Node.C1 (children.at 0)
|
||||
2 -> Node.C2 (children.at 0) (children.at 1)
|
||||
3 -> Node.C3 (children.at 0) (children.at 1) (children.at 2)
|
||||
4 -> Node.C4 (children.at 0) (children.at 1) (children.at 2) (children.at 3)
|
||||
5 -> Node.C5 (children.at 0) (children.at 1) (children.at 2) (children.at 3) (children.at 4)
|
||||
_ -> Error.throw ("Unexpected number of children: " + children_count.to_text)
|
||||
|
||||
|
||||
foreign js js_false = """
|
||||
return false
|
||||
|
||||
|
||||
foreign js js_true = """
|
||||
return true
|
||||
|
||||
foreign js js_text_foo = """
|
||||
return 'foo'
|
||||
|
||||
|
||||
spec =
|
||||
Test.group "Operator ==" <|
|
||||
Test.specify "should handle primitive values" <|
|
||||
(2 == (2.0)).should_be_true
|
||||
(2 == (2.1)).should_be_false
|
||||
(2.0).should_equal 2
|
||||
(js_true == True).should_be_true
|
||||
(js_false == False).should_be_true
|
||||
(js_true == False).should_be_false
|
||||
(js_text_foo == "foo").should_be_true
|
||||
|
||||
|
||||
Test.specify "should dispatch to overriden `==` on atoms" <|
|
||||
child1 = Child.Value 11
|
||||
parent1 = Parent.Value child1
|
||||
grand_parent1 = GrandParent.Value parent1
|
||||
|
||||
child2 = Child.Value 111
|
||||
parent2 = Parent.Value child2
|
||||
grand_parent2 = GrandParent.Value parent2
|
||||
|
||||
(grand_parent1 == grand_parent2).should_be_true
|
||||
|
||||
Test.specify "should handle `==` on types with many fields" <|
|
||||
many_fields1 = ManyFieldType.Value (Child.Value 1) (Child.Value 2) (Child.Value 3) (Child.Value 4) (Child.Value 5) (Child.Value 6) (Child.Value 7) (Child.Value 8) (Child.Value 9) (Child.Value 10) (Child.Value 11) (Child.Value 12) (Child.Value 13) (Child.Value 14) (Child.Value 15)
|
||||
many_fields2 = ManyFieldType.Value (Child.Value 101) (Child.Value 102) (Child.Value 103) (Child.Value 104) (Child.Value 105) (Child.Value 106) (Child.Value 107) (Child.Value 108) (Child.Value 109) (Child.Value 110) (Child.Value 111) (Child.Value 112) (Child.Value 113) (Child.Value 114) (Child.Value 115)
|
||||
|
||||
(many_fields1 == many_fields2).should_be_true
|
||||
|
||||
Test.specify "should be able to compare atoms with different constructors" <|
|
||||
((CustomEqType.C1 10) == (CustomEqType.C2 7 3)).should_be_true
|
||||
((CustomEqType.C1 0) == (CustomEqType.C2 7 3)).should_be_false
|
||||
|
||||
Test.specify "should dispatch to equals on host values" <|
|
||||
java_object1 = HashMap.new
|
||||
java_object1.put "a" 1
|
||||
java_object1.put "b" 2
|
||||
|
||||
java_object2 = HashMap.new
|
||||
java_object2.put "b" 2
|
||||
java_object2.put "a" 1
|
||||
|
||||
(java_object1 == java_object2).should_be_true
|
||||
|
||||
java_object2.put "c" 42
|
||||
|
||||
(java_object1 == java_object2).should_be_false
|
||||
|
||||
Test.specify "should return False for different Atoms with same fields" <|
|
||||
p1 = Point.Value 1 2
|
||||
p2 = Point.Value 3 4
|
||||
rect = Rect.Value p1 p2
|
||||
four_field = FourFieldType.Value 1 2 3 4
|
||||
(rect == four_field).should_be_false
|
||||
|
||||
Test.specify "should handle `==` on types" pending="Blocked by https://www.pivotaltracker.com/story/show/184092284" <|
|
||||
(Child == Child).should_be_true
|
||||
(Child == Point).should_be_false
|
||||
(Point == Child).should_be_false
|
||||
(HashMap == Child).should_be_false
|
||||
(Child == HashMap).should_be_false
|
||||
(Boolean == Any).should_be_false
|
||||
(Any == Boolean).should_be_false
|
||||
(Any == Any).should_be_true
|
||||
(Boolean == Boolean).should_be_true
|
||||
|
||||
Test.specify "should dispatch to overriden `==` in vectors" <|
|
||||
([(Child.Value 1)] == [(Child.Value 101)]).should_be_true
|
||||
([(Child.Value 1)] == [(Child.Value 2)]).should_be_false
|
||||
|
||||
Test.specify "should dispatch to overriden `==` in arrays" <|
|
||||
((Array.new_1 (Child.Value 1)) == (Array.new_1 (Child.Value 101))).should_be_true
|
||||
|
||||
Test.specify "should handle recursive atoms without custom `==`" <|
|
||||
rnd = (Random.new seed=42).java_random
|
||||
trees = (0.up_to 5).map _->
|
||||
create_random_tree 5 rnd
|
||||
trees.each tree->
|
||||
dupl_tree = tree.deep_copy
|
||||
Test.with_clue "Seed sed to 42" (tree == dupl_tree).should_be_true
|
||||
|
||||
|
||||
main = Test_Suite.run_main spec
|
@ -44,6 +44,10 @@ spec =
|
||||
f_2 = File.new "bar/../baz/../foo"
|
||||
f_2.normalize.should_equal f_1
|
||||
|
||||
Test.specify "should handle `==` on files" <|
|
||||
(File.new "foo").should_equal (File.new "foo")
|
||||
(File.new "bar").should_not_equal (File.new "foo")
|
||||
|
||||
Test.specify "should allow reading a file byte by byte" <|
|
||||
f = enso_project.data / "short.txt"
|
||||
f.delete_if_exists
|
||||
|
Loading…
Reference in New Issue
Block a user