mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 02:21:54 +03:00
Equality with conversions (#9070)
This commit is contained in:
parent
760afbc7f4
commit
a664dd9d56
@ -296,7 +296,7 @@ Number.should_equal : Float -> Float -> Integer -> Spec_Result
|
|||||||
Number.should_equal self that epsilon=0 frames_to_skip=0 =
|
Number.should_equal self that epsilon=0 frames_to_skip=0 =
|
||||||
matches = case that of
|
matches = case that of
|
||||||
_ : Number -> self.equals that epsilon
|
_ : Number -> self.equals that epsilon
|
||||||
_ -> False
|
_ -> self==that
|
||||||
case matches of
|
case matches of
|
||||||
True -> Spec_Result.Success
|
True -> Spec_Result.Success
|
||||||
False ->
|
False ->
|
||||||
|
@ -304,6 +304,44 @@ the addition by invoking `Num.+`. This behavior allows one to write libraries
|
|||||||
that extend existing number types with `Complex_Number`, `Rational_Number` and
|
that extend existing number types with `Complex_Number`, `Rational_Number` and
|
||||||
make them behave as first class citizen numbers.
|
make them behave as first class citizen numbers.
|
||||||
|
|
||||||
|
### Custom Equality
|
||||||
|
|
||||||
|
The `==` operator is special. A consistency with hash code is necessary to make
|
||||||
|
any Enso object behave correctly and work effectively in `Set` and `Map`
|
||||||
|
implementations. To guarantee such level of consistency there is a `Any.==`
|
||||||
|
definition providing _universal equality_ that **shall not be overriden**.
|
||||||
|
|
||||||
|
The `==` behavior is predefined for builtin types, atoms and other Enso objects.
|
||||||
|
In addition to that it remains possible to define own _comparators_, including a
|
||||||
|
comparator capable to work with already existing types. To create such
|
||||||
|
comparator define:
|
||||||
|
|
||||||
|
- conversion between existing type and the new type (as described in
|
||||||
|
[previous section](#type-ascriptions-and-operator-resolution))
|
||||||
|
- comparator (see documentation of `Ordering` type)
|
||||||
|
- define **two conversion method** that return the same comparator
|
||||||
|
|
||||||
|
To extend the previous definition of `Num` also for equality one might do for
|
||||||
|
example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
type Num_Comparator
|
||||||
|
compare a:Num b:Num = # compare somehow
|
||||||
|
hash a:Num = # hash somehow
|
||||||
|
|
||||||
|
Num.from (that:Integer) = # convert somehow
|
||||||
|
Comparable.from (_:Num) = Num_Comparator
|
||||||
|
Comparable.from (_:Integer) = Num_Comparator
|
||||||
|
```
|
||||||
|
|
||||||
|
with such a structure the internal implementation of `Any.==` performs necessary
|
||||||
|
conversions of `Integer` argument in case the other argument is `Num` and
|
||||||
|
invokes the `Num_Comparator.compare` to handle the comparision.
|
||||||
|
|
||||||
|
A care must be taken to keep consistency between `hash` values of original and
|
||||||
|
converted types - e.g. hash of `n:Integer` and hash of `Num.from n` must be the
|
||||||
|
same (otherwise consistency required for `Set` and `Map` would be compromised).
|
||||||
|
|
||||||
### Precedence
|
### Precedence
|
||||||
|
|
||||||
Operator precedence in Enso is a collection of rules that reflect conventions
|
Operator precedence in Enso is a collection of rules that reflect conventions
|
||||||
|
@ -63,10 +63,13 @@ another.
|
|||||||
|
|
||||||
## Multiple Dispatch
|
## Multiple Dispatch
|
||||||
|
|
||||||
It is an open question as to whether we want to support proper multiple dispatch
|
Multiple dispatch is currently used for
|
||||||
in Enso. Multiple dispatch refers to the dynamic dispatch target being
|
[binary operators](../syntax/functions.md#type-ascriptions-and-operator-resolution).
|
||||||
determined based not only on the type of the `this` argument, but the types of
|
|
||||||
the other arguments to the function.
|
Supporting it for general functions remains an open question as to whether we
|
||||||
|
want to support proper multiple dispatch in Enso. Multiple dispatch refers to
|
||||||
|
the dynamic dispatch target being determined based not only on the type of the
|
||||||
|
`this` argument, but the types of the other arguments to the function.
|
||||||
|
|
||||||
To do multiple dispatch properly, it is very important to get a rigorous
|
To do multiple dispatch properly, it is very important to get a rigorous
|
||||||
specification of the specificity algorithm. It must account for:
|
specification of the specificity algorithm. It must account for:
|
||||||
|
@ -75,6 +75,21 @@ public class EqualsBenchmarks {
|
|||||||
new StringBuilder(
|
new StringBuilder(
|
||||||
"""
|
"""
|
||||||
import Standard.Base.Data.Range.Extensions
|
import Standard.Base.Data.Range.Extensions
|
||||||
|
import Standard.Base.Data.Numbers.Number
|
||||||
|
import Standard.Base.Data.Ordering.Ordering
|
||||||
|
import Standard.Base.Data.Ordering.Comparable
|
||||||
|
import Standard.Base.Data.Ordering.Default_Comparator
|
||||||
|
|
||||||
|
type Num
|
||||||
|
Value n
|
||||||
|
|
||||||
|
Num.from (that:Number) = Num.Value that
|
||||||
|
Comparable.from (_:Number) = Num_Comparator
|
||||||
|
Comparable.from (_:Num) = Num_Comparator
|
||||||
|
|
||||||
|
type Num_Comparator
|
||||||
|
compare x:Num y:Num = Ordering.compare x.n y.n
|
||||||
|
hash x:Num = Default_Comparator.hash x.n
|
||||||
|
|
||||||
type Node
|
type Node
|
||||||
C1 f1
|
C1 f1
|
||||||
@ -87,7 +102,9 @@ public class EqualsBenchmarks {
|
|||||||
|
|
||||||
eq_vec vec1 vec2 =
|
eq_vec vec1 vec2 =
|
||||||
(0.up_to vec1.length).map idx->
|
(0.up_to vec1.length).map idx->
|
||||||
(vec1.at idx) == (vec2.at idx)
|
v1 = vec1.at idx
|
||||||
|
v2 = vec2.at idx
|
||||||
|
v1 == v2
|
||||||
|
|
||||||
eq x y = x == y
|
eq x y = x == y
|
||||||
""");
|
""");
|
||||||
@ -108,10 +125,37 @@ public class EqualsBenchmarks {
|
|||||||
primitiveVectorSize / 64);
|
primitiveVectorSize / 64);
|
||||||
codeBuilder
|
codeBuilder
|
||||||
.append(
|
.append(
|
||||||
generateVectorOfPrimitives(primitiveVectorSize, "vec1", 42, trueExpectedAt, random))
|
generateVectorOfPrimitives(
|
||||||
|
primitiveVectorSize, "vec1", 42, trueExpectedAt, random, "%d", "%f"))
|
||||||
.append("\n")
|
.append("\n")
|
||||||
.append(
|
.append(
|
||||||
generateVectorOfPrimitives(primitiveVectorSize, "vec2", 42, trueExpectedAt, random))
|
generateVectorOfPrimitives(
|
||||||
|
primitiveVectorSize, "vec2", 42, trueExpectedAt, random, "%d", "%f"))
|
||||||
|
.append("\n");
|
||||||
|
}
|
||||||
|
case "equalsWithConversion" -> {
|
||||||
|
trueExpectedAt =
|
||||||
|
Set.of(
|
||||||
|
primitiveVectorSize / 2,
|
||||||
|
primitiveVectorSize / 4,
|
||||||
|
primitiveVectorSize / 8,
|
||||||
|
primitiveVectorSize / 16,
|
||||||
|
primitiveVectorSize / 32,
|
||||||
|
primitiveVectorSize / 64);
|
||||||
|
codeBuilder
|
||||||
|
.append(
|
||||||
|
generateVectorOfPrimitives(
|
||||||
|
primitiveVectorSize, "vec1", 42, trueExpectedAt, random, "%d", "%f"))
|
||||||
|
.append("\n")
|
||||||
|
.append(
|
||||||
|
generateVectorOfPrimitives(
|
||||||
|
primitiveVectorSize,
|
||||||
|
"vec2",
|
||||||
|
42,
|
||||||
|
trueExpectedAt,
|
||||||
|
random,
|
||||||
|
"Num.Value %d",
|
||||||
|
"Num.Value %f"))
|
||||||
.append("\n");
|
.append("\n");
|
||||||
}
|
}
|
||||||
case "equalsStrings" -> {
|
case "equalsStrings" -> {
|
||||||
@ -141,7 +185,7 @@ public class EqualsBenchmarks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
codeBuilder.append("""
|
codeBuilder.append("""
|
||||||
bench x = eq_vec vec1 vec2
|
bench _ = eq_vec vec1 vec2
|
||||||
""");
|
""");
|
||||||
|
|
||||||
module = ctx.eval(SrcUtil.source(benchmarkName, codeBuilder.toString()));
|
module = ctx.eval(SrcUtil.source(benchmarkName, codeBuilder.toString()));
|
||||||
@ -171,6 +215,11 @@ public class EqualsBenchmarks {
|
|||||||
performBenchmark(blackHole);
|
performBenchmark(blackHole);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public void equalsWithConversion(Blackhole blackHole) {
|
||||||
|
performBenchmark(blackHole);
|
||||||
|
}
|
||||||
|
|
||||||
@Benchmark
|
@Benchmark
|
||||||
public void equalsStrings(Blackhole blackhole) {
|
public void equalsStrings(Blackhole blackhole) {
|
||||||
performBenchmark(blackhole);
|
performBenchmark(blackhole);
|
||||||
@ -207,7 +256,9 @@ public class EqualsBenchmarks {
|
|||||||
String vecName,
|
String vecName,
|
||||||
Object identityElem,
|
Object identityElem,
|
||||||
Collection<Integer> constantIdxs,
|
Collection<Integer> constantIdxs,
|
||||||
Random random) {
|
Random random,
|
||||||
|
String intFormat,
|
||||||
|
String doubleFormat) {
|
||||||
var partSize = totalSize / 2;
|
var partSize = totalSize / 2;
|
||||||
List<Object> primitiveValues = new ArrayList<>();
|
List<Object> primitiveValues = new ArrayList<>();
|
||||||
random.ints(partSize).forEach(primitiveValues::add);
|
random.ints(partSize).forEach(primitiveValues::add);
|
||||||
@ -221,9 +272,9 @@ public class EqualsBenchmarks {
|
|||||||
sb.append(vecName).append(" = [");
|
sb.append(vecName).append(" = [");
|
||||||
for (Object primitiveValue : primitiveValues) {
|
for (Object primitiveValue : primitiveValues) {
|
||||||
if (primitiveValue instanceof Double dbl) {
|
if (primitiveValue instanceof Double dbl) {
|
||||||
sb.append(String.format("%f", dbl)).append(",");
|
sb.append(String.format(doubleFormat, dbl)).append(",");
|
||||||
} else {
|
} else {
|
||||||
sb.append(primitiveValue).append(",");
|
sb.append(String.format(intFormat, primitiveValue)).append(",");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Replace last comma
|
// Replace last comma
|
||||||
|
@ -0,0 +1,174 @@
|
|||||||
|
package org.enso.interpreter.test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.graalvm.polyglot.Context;
|
||||||
|
import org.graalvm.polyglot.PolyglotException;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class EqualsConversionsTest extends TestBase {
|
||||||
|
private static Context context;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void initContextAndData() {
|
||||||
|
context = createDefaultContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void disposeContext() {
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBasicInequalities() {
|
||||||
|
var results =
|
||||||
|
TestBase.evalModule(
|
||||||
|
context,
|
||||||
|
"""
|
||||||
|
from Standard.Base import all
|
||||||
|
|
||||||
|
Text.from (that:Number) = that.to_text
|
||||||
|
|
||||||
|
main =
|
||||||
|
r0 = "4"+"2" == "42"
|
||||||
|
r1 = 42 == "42"
|
||||||
|
r2 = "42" == 42
|
||||||
|
[ r0, r1, r2 ]
|
||||||
|
""")
|
||||||
|
.as(List.class);
|
||||||
|
|
||||||
|
assertTrue("strings are equal: " + results, (boolean) results.get(0));
|
||||||
|
assertFalse("string is not equal to number: " + results, (boolean) results.get(1));
|
||||||
|
assertFalse("number is not equal to string: " + results, (boolean) results.get(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNumWrapperAroundIntegerIsEqualToInteger() {
|
||||||
|
var gen = new DefineComparableWrapper();
|
||||||
|
gen.intNumConversion = true;
|
||||||
|
gen.intComparator = true;
|
||||||
|
gen.numComparator = true;
|
||||||
|
assertTrue("Num.Value equal to Integer: ", gen.evaluate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMissingIntegerNumConversion() {
|
||||||
|
var gen = new DefineComparableWrapper();
|
||||||
|
gen.intNumConversion = false;
|
||||||
|
gen.intComparator = true;
|
||||||
|
gen.numComparator = true;
|
||||||
|
assertFalse("Num.Value not equal to Integer: ", gen.evaluate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMissingIntComparator() {
|
||||||
|
var gen = new DefineComparableWrapper();
|
||||||
|
gen.intNumConversion = true;
|
||||||
|
gen.intComparator = false;
|
||||||
|
gen.numComparator = true;
|
||||||
|
assertFalse("Num.Value not equal to Integer: ", gen.evaluate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMissingNumComparator() {
|
||||||
|
var gen = new DefineComparableWrapper();
|
||||||
|
gen.intNumConversion = true;
|
||||||
|
gen.intComparator = true;
|
||||||
|
gen.numComparator = false;
|
||||||
|
assertFalse("Num.Value not equal to Integer: ", gen.evaluate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDifferentComparators() {
|
||||||
|
var gen = new DefineComparableWrapper();
|
||||||
|
gen.intNumConversion = true;
|
||||||
|
gen.intComparator = true;
|
||||||
|
gen.numComparator = false;
|
||||||
|
gen.extraBlock =
|
||||||
|
"""
|
||||||
|
type Second_Comparator
|
||||||
|
compare a:Num b:Num = Num_Comparator.compare a b
|
||||||
|
hash a:Num = Num_Comparator.hash a
|
||||||
|
|
||||||
|
Comparable.from (_:Num) = Second_Comparator
|
||||||
|
""";
|
||||||
|
assertFalse("Num.Value not equal to Integer: ", gen.evaluate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInconsistentHashFunction() {
|
||||||
|
var gen = new DefineComparableWrapper();
|
||||||
|
gen.intNumConversion = true;
|
||||||
|
gen.intComparator = true;
|
||||||
|
gen.numComparator = true;
|
||||||
|
gen.hashFn = "x.n*31";
|
||||||
|
|
||||||
|
boolean r;
|
||||||
|
try {
|
||||||
|
r = gen.evaluate();
|
||||||
|
} catch (PolyglotException ex) {
|
||||||
|
assertTrue(ex.getMessage(), ex.getMessage().contains("Different hash code!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fail("Expecting assertion error, not: " + r);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class DefineComparableWrapper {
|
||||||
|
boolean intNumConversion;
|
||||||
|
boolean numComparator;
|
||||||
|
boolean intComparator;
|
||||||
|
String hashFn = "Default_Comparator.hash x.n";
|
||||||
|
String extraBlock = "";
|
||||||
|
|
||||||
|
boolean evaluate() {
|
||||||
|
var block0 =
|
||||||
|
"""
|
||||||
|
from Standard.Base import all
|
||||||
|
|
||||||
|
type Num
|
||||||
|
Value n:Integer
|
||||||
|
|
||||||
|
type Num_Comparator
|
||||||
|
compare x:Num y:Num = Ordering.compare x.n y.n
|
||||||
|
hash x:Num = {hashFn}
|
||||||
|
"""
|
||||||
|
.replace("{hashFn}", hashFn);
|
||||||
|
|
||||||
|
var block1 =
|
||||||
|
!intNumConversion
|
||||||
|
? ""
|
||||||
|
: """
|
||||||
|
Num.from (that:Integer) = Num.Value that
|
||||||
|
""";
|
||||||
|
|
||||||
|
var block2 =
|
||||||
|
!numComparator
|
||||||
|
? ""
|
||||||
|
: """
|
||||||
|
Comparable.from (_:Num) = Num_Comparator
|
||||||
|
""";
|
||||||
|
|
||||||
|
var block3 =
|
||||||
|
!intComparator ? "" : """
|
||||||
|
Comparable.from (_:Integer) = Num_Comparator
|
||||||
|
""";
|
||||||
|
|
||||||
|
var mainBlock =
|
||||||
|
"""
|
||||||
|
main =
|
||||||
|
num42 = Num.Value 42
|
||||||
|
|
||||||
|
r0 = 42 == num42
|
||||||
|
r0
|
||||||
|
""";
|
||||||
|
var res =
|
||||||
|
TestBase.evalModule(context, block0 + block1 + block2 + block3 + mainBlock + extraBlock);
|
||||||
|
return res.asBoolean();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ import static org.junit.Assert.assertNotEquals;
|
|||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
@ -16,7 +17,6 @@ import java.util.List;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode;
|
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.EqualsNode;
|
||||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsNodeGen;
|
|
||||||
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
|
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
|
||||||
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||||
import org.enso.polyglot.MethodNames;
|
import org.enso.polyglot.MethodNames;
|
||||||
@ -44,8 +44,8 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
testRootNode = new TestRootNode();
|
testRootNode = new TestRootNode(EqualsTest::equalityCheck);
|
||||||
equalsNode = EqualsNode.build();
|
equalsNode = EqualsNode.create();
|
||||||
hostValueToEnsoNode = HostValueToEnsoNode.build();
|
hostValueToEnsoNode = HostValueToEnsoNode.build();
|
||||||
testRootNode.insertChildren(equalsNode, hostValueToEnsoNode);
|
testRootNode.insertChildren(equalsNode, hostValueToEnsoNode);
|
||||||
return null;
|
return null;
|
||||||
@ -88,13 +88,22 @@ public class EqualsTest extends TestBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean equalityCheck(VirtualFrame frame) {
|
||||||
|
var args = frame.getArguments();
|
||||||
|
return equalsNode.execute(frame, args[0], args[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean equalityCheck(Object first, Object second) {
|
||||||
|
return (Boolean) testRootNode.getCallTarget().call(first, second);
|
||||||
|
}
|
||||||
|
|
||||||
@Theory
|
@Theory
|
||||||
public void equalsOperatorShouldBeSymmetric(Object firstValue, Object secondValue) {
|
public void equalsOperatorShouldBeSymmetric(Object firstValue, Object secondValue) {
|
||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
boolean firstResult = equalsNode.execute(firstValue, secondValue);
|
boolean firstResult = equalityCheck(firstValue, secondValue);
|
||||||
boolean secondResult = equalsNode.execute(secondValue, firstValue);
|
boolean secondResult = equalityCheck(secondValue, firstValue);
|
||||||
assertEquals("equals should be symmetric", firstResult, secondResult);
|
assertEquals("equals should be symmetric", firstResult, secondResult);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
@ -105,8 +114,8 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
Object firstResult = equalsNode.execute(value, value);
|
Object firstResult = equalityCheck(value, value);
|
||||||
Object secondResult = equalsNode.execute(value, value);
|
Object secondResult = equalityCheck(value, value);
|
||||||
assertEquals("equals should be consistent", firstResult, secondResult);
|
assertEquals("equals should be consistent", firstResult, secondResult);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
@ -117,8 +126,8 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
Object uncachedRes = EqualsNodeGen.getUncached().execute(firstVal, secondVal);
|
Object uncachedRes = EqualsNode.getUncached().execute(null, firstVal, secondVal);
|
||||||
Object cachedRes = equalsNode.execute(firstVal, secondVal);
|
Object cachedRes = equalityCheck(firstVal, secondVal);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"Result from uncached EqualsNode should be the same as result from its cached"
|
"Result from uncached EqualsNode should be the same as result from its cached"
|
||||||
+ " variant",
|
+ " variant",
|
||||||
@ -140,7 +149,7 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
assertTrue(equalsNode.execute(ensoDate, javaDate));
|
assertTrue(equalityCheck(ensoDate, javaDate));
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -158,7 +167,7 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
assertTrue(equalsNode.execute(ensoTime, javaDate));
|
assertTrue(equalityCheck(ensoTime, javaDate));
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -181,7 +190,7 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
assertTrue(equalsNode.execute(ensoDateTime, javaDateTime));
|
assertTrue(equalityCheck(ensoDateTime, javaDateTime));
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -194,7 +203,7 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
assertTrue(javaNumber + " == " + ensoNumber, equalsNode.execute(javaNumber, ensoNumber));
|
assertTrue(javaNumber + " == " + ensoNumber, equalityCheck(javaNumber, ensoNumber));
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -207,7 +216,7 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
assertTrue(ensoNumber + " == " + javaNumber, equalsNode.execute(ensoNumber, javaNumber));
|
assertTrue(ensoNumber + " == " + javaNumber, equalityCheck(ensoNumber, javaNumber));
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -220,7 +229,7 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
assertTrue(javaNumber + " == " + hostNumber, equalsNode.execute(javaNumber, hostNumber));
|
assertTrue(javaNumber + " == " + hostNumber, equalityCheck(javaNumber, hostNumber));
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -233,7 +242,7 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
assertTrue(hostNumber + " == " + javaNumber, equalsNode.execute(hostNumber, javaNumber));
|
assertTrue(hostNumber + " == " + javaNumber, equalityCheck(hostNumber, javaNumber));
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -246,7 +255,7 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
assertTrue(equalsNode.execute(ensoVector, javaVector));
|
assertTrue(equalityCheck(ensoVector, javaVector));
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -258,9 +267,9 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
assertTrue(equalsNode.execute(ensoNumber, foreignNumber.asDirect()));
|
assertTrue(equalityCheck(ensoNumber, foreignNumber.asDirect()));
|
||||||
assertTrue(equalsNode.execute(ensoNumber, foreignNumber));
|
assertTrue(equalityCheck(ensoNumber, foreignNumber));
|
||||||
assertTrue(equalsNode.execute(foreignNumber, ensoNumber));
|
assertTrue(equalityCheck(foreignNumber, ensoNumber));
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -272,9 +281,9 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
assertTrue(equalsNode.execute(ensoNumber, foreignNumber.asDirect()));
|
assertTrue(equalityCheck(ensoNumber, foreignNumber.asDirect()));
|
||||||
assertTrue(equalsNode.execute(ensoNumber, foreignNumber));
|
assertTrue(equalityCheck(ensoNumber, foreignNumber));
|
||||||
assertTrue(equalsNode.execute(foreignNumber, ensoNumber));
|
assertTrue(equalityCheck(foreignNumber, ensoNumber));
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -287,8 +296,8 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
assertTrue(equalsNode.execute(ensoNumber, foreignNumber));
|
assertTrue(equalityCheck(ensoNumber, foreignNumber));
|
||||||
assertTrue(equalsNode.execute(foreignNumber, ensoNumber));
|
assertTrue(equalityCheck(foreignNumber, ensoNumber));
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -301,9 +310,9 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
assertTrue(equalsNode.execute(ensoBoolean, foreignBoolean.asDirect()));
|
assertTrue(equalityCheck(ensoBoolean, foreignBoolean.asDirect()));
|
||||||
assertTrue(equalsNode.execute(ensoBoolean, foreignBoolean));
|
assertTrue(equalityCheck(ensoBoolean, foreignBoolean));
|
||||||
assertTrue(equalsNode.execute(foreignBoolean, ensoBoolean));
|
assertTrue(equalityCheck(foreignBoolean, ensoBoolean));
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -315,9 +324,9 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
assertTrue(equalsNode.execute(ensoText, foreignString.asDirect()));
|
assertTrue(equalityCheck(ensoText, foreignString.asDirect()));
|
||||||
assertTrue(equalsNode.execute(ensoText, foreignString));
|
assertTrue(equalityCheck(ensoText, foreignString));
|
||||||
assertTrue(equalsNode.execute(foreignString, ensoText));
|
assertTrue(equalityCheck(foreignString, ensoText));
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -336,8 +345,8 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
assertTrue(equalsNode.execute(142L, hundred42));
|
assertTrue(equalityCheck(142L, hundred42));
|
||||||
assertTrue(equalsNode.execute(hundred42, 142L));
|
assertTrue(equalityCheck(hundred42, 142L));
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -375,15 +384,12 @@ public class EqualsTest extends TestBase {
|
|||||||
executeInContext(
|
executeInContext(
|
||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
assertTrue(
|
assertTrue("Conversions from same module are the same", equalityCheck(conv1, conv1_2));
|
||||||
"Conversions from same module are the same", equalsNode.execute(conv1, conv1_2));
|
assertTrue("Conversions from same module are the same", equalityCheck(conv2, conv2_2));
|
||||||
assertTrue(
|
|
||||||
"Conversions from same module are the same", equalsNode.execute(conv2, conv2_2));
|
|
||||||
assertFalse(
|
assertFalse(
|
||||||
"Conversions from other modules aren't the same", equalsNode.execute(conv1, conv2));
|
"Conversions from other modules aren't the same", equalityCheck(conv1, conv2));
|
||||||
assertFalse(
|
assertFalse(
|
||||||
"Conversions from other modueles aren't the same",
|
"Conversions from other modueles aren't the same", equalityCheck(conv2_2, conv1_2));
|
||||||
equalsNode.execute(conv2_2, conv1_2));
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ public class HashCodeTest extends TestBase {
|
|||||||
context,
|
context,
|
||||||
() -> {
|
() -> {
|
||||||
hashCodeNode = HashCodeNode.build();
|
hashCodeNode = HashCodeNode.build();
|
||||||
equalsNode = EqualsNode.build();
|
equalsNode = EqualsNode.create();
|
||||||
hostValueToEnsoNode = HostValueToEnsoNode.build();
|
hostValueToEnsoNode = HostValueToEnsoNode.build();
|
||||||
testRootNode = new TestRootNode();
|
testRootNode = new TestRootNode();
|
||||||
testRootNode.insertChildren(hashCodeNode, equalsNode, hostValueToEnsoNode);
|
testRootNode.insertChildren(hashCodeNode, equalsNode, hostValueToEnsoNode);
|
||||||
@ -94,7 +94,7 @@ public class HashCodeTest extends TestBase {
|
|||||||
() -> {
|
() -> {
|
||||||
long firstHash = hashCodeNode.execute(firstValue);
|
long firstHash = hashCodeNode.execute(firstValue);
|
||||||
long secondHash = hashCodeNode.execute(secondValue);
|
long secondHash = hashCodeNode.execute(secondValue);
|
||||||
Object valuesAreEqual = equalsNode.execute(firstValue, secondValue);
|
Object valuesAreEqual = equalsNode.execute(null, firstValue, secondValue);
|
||||||
// if o1 == o2 then hash(o1) == hash(o2)
|
// if o1 == o2 then hash(o1) == hash(o2)
|
||||||
if (isTrue(valuesAreEqual)) {
|
if (isTrue(valuesAreEqual)) {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -13,6 +13,7 @@ import java.io.OutputStream;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import org.enso.interpreter.EnsoLanguage;
|
import org.enso.interpreter.EnsoLanguage;
|
||||||
import org.enso.polyglot.MethodNames.Module;
|
import org.enso.polyglot.MethodNames.Module;
|
||||||
@ -45,6 +46,7 @@ public abstract class TestBase {
|
|||||||
.allowIO(IOAccess.ALL)
|
.allowIO(IOAccess.ALL)
|
||||||
.allowAllAccess(true)
|
.allowAllAccess(true)
|
||||||
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
|
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
|
||||||
|
.option(RuntimeOptions.DISABLE_IR_CACHES, "true")
|
||||||
.logHandler(System.err)
|
.logHandler(System.err)
|
||||||
.option(RuntimeOptions.STRICT_ERRORS, "true")
|
.option(RuntimeOptions.STRICT_ERRORS, "true")
|
||||||
.option(
|
.option(
|
||||||
@ -152,8 +154,15 @@ public abstract class TestBase {
|
|||||||
* #insertChildren(Node...)}.
|
* #insertChildren(Node...)}.
|
||||||
*/
|
*/
|
||||||
static class TestRootNode extends RootNode {
|
static class TestRootNode extends RootNode {
|
||||||
|
private final Function<VirtualFrame, Object> callback;
|
||||||
|
|
||||||
TestRootNode() {
|
TestRootNode() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
TestRootNode(Function<VirtualFrame, Object> callback) {
|
||||||
super(EnsoLanguage.get(null));
|
super(EnsoLanguage.get(null));
|
||||||
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void insertChildren(Node... children) {
|
void insertChildren(Node... children) {
|
||||||
@ -165,7 +174,11 @@ public abstract class TestBase {
|
|||||||
/** In the tests, do not execute this root node, but execute directly the child nodes. */
|
/** In the tests, do not execute this root node, but execute directly the child nodes. */
|
||||||
@Override
|
@Override
|
||||||
public Object execute(VirtualFrame frame) {
|
public Object execute(VirtualFrame frame) {
|
||||||
|
if (callback == null) {
|
||||||
throw new AssertionError("should not reach here");
|
throw new AssertionError("should not reach here");
|
||||||
|
} else {
|
||||||
|
return callback.apply(frame);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ import com.oracle.truffle.api.dsl.Cached;
|
|||||||
import com.oracle.truffle.api.dsl.Cached.Shared;
|
import com.oracle.truffle.api.dsl.Cached.Shared;
|
||||||
import com.oracle.truffle.api.dsl.GenerateUncached;
|
import com.oracle.truffle.api.dsl.GenerateUncached;
|
||||||
import com.oracle.truffle.api.dsl.Specialization;
|
import com.oracle.truffle.api.dsl.Specialization;
|
||||||
|
import com.oracle.truffle.api.frame.MaterializedFrame;
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
import com.oracle.truffle.api.library.CachedLibrary;
|
import com.oracle.truffle.api.library.CachedLibrary;
|
||||||
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
import com.oracle.truffle.api.nodes.ExplodeLoop;
|
||||||
import com.oracle.truffle.api.nodes.Node;
|
import com.oracle.truffle.api.nodes.Node;
|
||||||
@ -32,7 +34,7 @@ public abstract class EqualsAtomNode extends Node {
|
|||||||
return EqualsAtomNodeGen.create();
|
return EqualsAtomNodeGen.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract boolean execute(Atom left, Atom right);
|
public abstract boolean execute(VirtualFrame frame, Atom left, Atom right);
|
||||||
|
|
||||||
static EqualsNode[] createEqualsNodes(int size) {
|
static EqualsNode[] createEqualsNodes(int size) {
|
||||||
EqualsNode[] nodes = new EqualsNode[size];
|
EqualsNode[] nodes = new EqualsNode[size];
|
||||||
@ -48,6 +50,7 @@ public abstract class EqualsAtomNode extends Node {
|
|||||||
limit = "10")
|
limit = "10")
|
||||||
@ExplodeLoop
|
@ExplodeLoop
|
||||||
boolean equalsAtomsWithDefaultComparator(
|
boolean equalsAtomsWithDefaultComparator(
|
||||||
|
VirtualFrame frame,
|
||||||
Atom self,
|
Atom self,
|
||||||
Atom other,
|
Atom other,
|
||||||
@Cached("self.getConstructor()") AtomConstructor selfCtorCached,
|
@Cached("self.getConstructor()") AtomConstructor selfCtorCached,
|
||||||
@ -65,7 +68,7 @@ public abstract class EqualsAtomNode extends Node {
|
|||||||
for (int i = 0; i < fieldsLenCached; i++) {
|
for (int i = 0; i < fieldsLenCached; i++) {
|
||||||
var selfValue = structsLib.getField(self, i);
|
var selfValue = structsLib.getField(self, i);
|
||||||
var otherValue = structsLib.getField(other, i);
|
var otherValue = structsLib.getField(other, i);
|
||||||
var fieldsAreEqual = fieldEqualsNodes[i].execute(selfValue, otherValue);
|
var fieldsAreEqual = fieldEqualsNodes[i].execute(frame, selfValue, otherValue);
|
||||||
if (!fieldsAreEqual) {
|
if (!fieldsAreEqual) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -116,13 +119,18 @@ public abstract class EqualsAtomNode extends Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@CompilerDirectives.TruffleBoundary
|
|
||||||
@Specialization(
|
@Specialization(
|
||||||
replaces = {"equalsAtomsWithDefaultComparator", "equalsAtomsWithCustomComparator"})
|
replaces = {"equalsAtomsWithDefaultComparator", "equalsAtomsWithCustomComparator"})
|
||||||
boolean equalsAtomsUncached(Atom self, Atom other) {
|
boolean equalsAtomsUncached(VirtualFrame frame, Atom self, Atom other) {
|
||||||
if (self.getConstructor() != other.getConstructor()) {
|
if (self.getConstructor() != other.getConstructor()) {
|
||||||
return false;
|
return false;
|
||||||
|
} else {
|
||||||
|
return equalsAtomsUncached(frame == null ? null : frame.materialize(), self, other);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CompilerDirectives.TruffleBoundary
|
||||||
|
private boolean equalsAtomsUncached(MaterializedFrame frame, Atom self, Atom other) {
|
||||||
Type customComparator = CustomComparatorNode.getUncached().execute(self);
|
Type customComparator = CustomComparatorNode.getUncached().execute(self);
|
||||||
if (customComparator != null) {
|
if (customComparator != null) {
|
||||||
Function compareFunc = findCompareMethod(customComparator);
|
Function compareFunc = findCompareMethod(customComparator);
|
||||||
@ -139,7 +147,7 @@ public abstract class EqualsAtomNode extends Node {
|
|||||||
for (int i = 0; i < self.getConstructor().getArity(); i++) {
|
for (int i = 0; i < self.getConstructor().getArity(); i++) {
|
||||||
var selfField = StructsLibrary.getUncached().getField(self, i);
|
var selfField = StructsLibrary.getUncached().getField(self, i);
|
||||||
var otherField = StructsLibrary.getUncached().getField(other, i);
|
var otherField = StructsLibrary.getUncached().getField(other, i);
|
||||||
boolean areFieldsSame = EqualsNodeGen.getUncached().execute(selfField, otherField);
|
boolean areFieldsSame = EqualsNode.getUncached().execute(frame, selfField, otherField);
|
||||||
if (!areFieldsSame) {
|
if (!areFieldsSame) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import com.oracle.truffle.api.dsl.Cached.Exclusive;
|
|||||||
import com.oracle.truffle.api.dsl.Cached.Shared;
|
import com.oracle.truffle.api.dsl.Cached.Shared;
|
||||||
import com.oracle.truffle.api.dsl.GenerateUncached;
|
import com.oracle.truffle.api.dsl.GenerateUncached;
|
||||||
import com.oracle.truffle.api.dsl.Specialization;
|
import com.oracle.truffle.api.dsl.Specialization;
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
import com.oracle.truffle.api.interop.ArityException;
|
import com.oracle.truffle.api.interop.ArityException;
|
||||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||||
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
|
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
|
||||||
@ -18,9 +19,7 @@ import com.oracle.truffle.api.library.CachedLibrary;
|
|||||||
import com.oracle.truffle.api.nodes.Node;
|
import com.oracle.truffle.api.nodes.Node;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import org.enso.interpreter.dsl.AcceptsError;
|
|
||||||
import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode;
|
import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode;
|
||||||
import org.enso.interpreter.node.expression.builtin.ordering.CustomComparatorNode;
|
|
||||||
import org.enso.interpreter.runtime.EnsoContext;
|
import org.enso.interpreter.runtime.EnsoContext;
|
||||||
import org.enso.interpreter.runtime.Module;
|
import org.enso.interpreter.runtime.Module;
|
||||||
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
|
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
|
||||||
@ -39,24 +38,26 @@ public abstract class EqualsComplexNode extends Node {
|
|||||||
return EqualsComplexNodeGen.create();
|
return EqualsComplexNodeGen.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract boolean execute(@AcceptsError Object left, @AcceptsError Object right);
|
public abstract boolean execute(VirtualFrame frame, Object left, Object right);
|
||||||
|
|
||||||
/** Enso specific types */
|
/** Enso specific types */
|
||||||
@Specialization
|
@Specialization
|
||||||
boolean equalsUnresolvedSymbols(
|
boolean equalsUnresolvedSymbols(
|
||||||
|
VirtualFrame frame,
|
||||||
UnresolvedSymbol self,
|
UnresolvedSymbol self,
|
||||||
UnresolvedSymbol otherSymbol,
|
UnresolvedSymbol otherSymbol,
|
||||||
@Shared("equalsNode") @Cached EqualsNode equalsNode) {
|
@Shared("equalsNode") @Cached EqualsNode equalsNode) {
|
||||||
return self.getName().equals(otherSymbol.getName())
|
return self.getName().equals(otherSymbol.getName())
|
||||||
&& equalsNode.execute(self.getScope(), otherSymbol.getScope());
|
&& equalsNode.execute(frame, self.getScope(), otherSymbol.getScope());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization
|
@Specialization
|
||||||
boolean equalsUnresolvedConversion(
|
boolean equalsUnresolvedConversion(
|
||||||
|
VirtualFrame frame,
|
||||||
UnresolvedConversion selfConversion,
|
UnresolvedConversion selfConversion,
|
||||||
UnresolvedConversion otherConversion,
|
UnresolvedConversion otherConversion,
|
||||||
@Shared("equalsNode") @Cached EqualsNode equalsNode) {
|
@Shared("equalsNode") @Cached EqualsNode equalsNode) {
|
||||||
return equalsNode.execute(selfConversion.getScope(), otherConversion.getScope());
|
return equalsNode.execute(frame, selfConversion.getScope(), otherConversion.getScope());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization
|
@Specialization
|
||||||
@ -71,8 +72,11 @@ public abstract class EqualsComplexNode extends Node {
|
|||||||
|
|
||||||
@Specialization
|
@Specialization
|
||||||
boolean equalsFiles(
|
boolean equalsFiles(
|
||||||
EnsoFile selfFile, EnsoFile otherFile, @Shared("equalsNode") @Cached EqualsNode equalsNode) {
|
VirtualFrame frame,
|
||||||
return equalsNode.execute(selfFile.getPath(), otherFile.getPath());
|
EnsoFile selfFile,
|
||||||
|
EnsoFile otherFile,
|
||||||
|
@Shared("equalsNode") @Cached EqualsNode equalsNode) {
|
||||||
|
return equalsNode.execute(frame, selfFile.getPath(), otherFile.getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,12 +86,13 @@ public abstract class EqualsComplexNode extends Node {
|
|||||||
*/
|
*/
|
||||||
@Specialization(guards = {"typesLib.hasType(selfType)", "typesLib.hasType(otherType)"})
|
@Specialization(guards = {"typesLib.hasType(selfType)", "typesLib.hasType(otherType)"})
|
||||||
boolean equalsTypes(
|
boolean equalsTypes(
|
||||||
|
VirtualFrame frame,
|
||||||
Type selfType,
|
Type selfType,
|
||||||
Type otherType,
|
Type otherType,
|
||||||
@Shared("equalsNode") @Cached EqualsNode equalsNode,
|
@Shared("equalsNode") @Cached EqualsNode equalsNode,
|
||||||
@Shared("typesLib") @CachedLibrary(limit = "10") TypesLibrary typesLib) {
|
@Shared("typesLib") @CachedLibrary(limit = "10") TypesLibrary typesLib) {
|
||||||
return equalsNode.execute(
|
return equalsNode.execute(
|
||||||
selfType.getQualifiedName().toString(), otherType.getQualifiedName().toString());
|
frame, selfType.getQualifiedName().toString(), otherType.getQualifiedName().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,6 +104,7 @@ public abstract class EqualsComplexNode extends Node {
|
|||||||
},
|
},
|
||||||
limit = "3")
|
limit = "3")
|
||||||
boolean equalsWithWarnings(
|
boolean equalsWithWarnings(
|
||||||
|
VirtualFrame frame,
|
||||||
Object selfWithWarnings,
|
Object selfWithWarnings,
|
||||||
Object otherWithWarnings,
|
Object otherWithWarnings,
|
||||||
@CachedLibrary("selfWithWarnings") WarningsLibrary selfWarnLib,
|
@CachedLibrary("selfWithWarnings") WarningsLibrary selfWarnLib,
|
||||||
@ -113,7 +119,7 @@ public abstract class EqualsComplexNode extends Node {
|
|||||||
otherWarnLib.hasWarnings(otherWithWarnings)
|
otherWarnLib.hasWarnings(otherWithWarnings)
|
||||||
? otherWarnLib.removeWarnings(otherWithWarnings)
|
? otherWarnLib.removeWarnings(otherWithWarnings)
|
||||||
: otherWithWarnings;
|
: otherWithWarnings;
|
||||||
return equalsNode.execute(self, other);
|
return equalsNode.execute(frame, self, other);
|
||||||
} catch (UnsupportedMessageException e) {
|
} catch (UnsupportedMessageException e) {
|
||||||
throw EnsoContext.get(this).raiseAssertionPanic(this, null, e);
|
throw EnsoContext.get(this).raiseAssertionPanic(this, null, e);
|
||||||
}
|
}
|
||||||
@ -278,12 +284,12 @@ public abstract class EqualsComplexNode extends Node {
|
|||||||
},
|
},
|
||||||
limit = "3")
|
limit = "3")
|
||||||
boolean equalsArrays(
|
boolean equalsArrays(
|
||||||
|
VirtualFrame frame,
|
||||||
Object selfArray,
|
Object selfArray,
|
||||||
Object otherArray,
|
Object otherArray,
|
||||||
@CachedLibrary("selfArray") InteropLibrary selfInterop,
|
@CachedLibrary("selfArray") InteropLibrary selfInterop,
|
||||||
@CachedLibrary("otherArray") InteropLibrary otherInterop,
|
@CachedLibrary("otherArray") InteropLibrary otherInterop,
|
||||||
@Shared("equalsNode") @Cached EqualsNode equalsNode,
|
@Shared("equalsNode") @Cached EqualsNode equalsNode,
|
||||||
@Cached CustomComparatorNode hasCustomComparatorNode,
|
|
||||||
@Shared("hostValueToEnsoNode") @Cached HostValueToEnsoNode valueToEnsoNode) {
|
@Shared("hostValueToEnsoNode") @Cached HostValueToEnsoNode valueToEnsoNode) {
|
||||||
try {
|
try {
|
||||||
long selfSize = selfInterop.getArraySize(selfArray);
|
long selfSize = selfInterop.getArraySize(selfArray);
|
||||||
@ -293,7 +299,7 @@ public abstract class EqualsComplexNode extends Node {
|
|||||||
for (long i = 0; i < selfSize; i++) {
|
for (long i = 0; i < selfSize; i++) {
|
||||||
Object selfElem = valueToEnsoNode.execute(selfInterop.readArrayElement(selfArray, i));
|
Object selfElem = valueToEnsoNode.execute(selfInterop.readArrayElement(selfArray, i));
|
||||||
Object otherElem = valueToEnsoNode.execute(otherInterop.readArrayElement(otherArray, i));
|
Object otherElem = valueToEnsoNode.execute(otherInterop.readArrayElement(otherArray, i));
|
||||||
boolean elemsAreEqual = equalsNode.execute(selfElem, otherElem);
|
boolean elemsAreEqual = equalsNode.execute(frame, selfElem, otherElem);
|
||||||
if (!elemsAreEqual) {
|
if (!elemsAreEqual) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -313,6 +319,7 @@ public abstract class EqualsComplexNode extends Node {
|
|||||||
},
|
},
|
||||||
limit = "3")
|
limit = "3")
|
||||||
boolean equalsHashMaps(
|
boolean equalsHashMaps(
|
||||||
|
VirtualFrame frame,
|
||||||
Object selfHashMap,
|
Object selfHashMap,
|
||||||
Object otherHashMap,
|
Object otherHashMap,
|
||||||
@CachedLibrary("selfHashMap") InteropLibrary selfInterop,
|
@CachedLibrary("selfHashMap") InteropLibrary selfInterop,
|
||||||
@ -337,7 +344,7 @@ public abstract class EqualsComplexNode extends Node {
|
|||||||
&& otherInterop.isHashEntryReadable(otherHashMap, key)) {
|
&& otherInterop.isHashEntryReadable(otherHashMap, key)) {
|
||||||
Object otherValue =
|
Object otherValue =
|
||||||
valueToEnsoNode.execute(otherInterop.readHashValue(otherHashMap, key));
|
valueToEnsoNode.execute(otherInterop.readHashValue(otherHashMap, key));
|
||||||
if (!equalsNode.execute(selfValue, otherValue)) {
|
if (!equalsNode.execute(frame, selfValue, otherValue)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -391,13 +398,14 @@ public abstract class EqualsComplexNode extends Node {
|
|||||||
// It has well-defined equality based on the qualified name.
|
// It has well-defined equality based on the qualified name.
|
||||||
@Specialization(guards = {"isJavaFunction(selfHostFunc)", "isJavaFunction(otherHostFunc)"})
|
@Specialization(guards = {"isJavaFunction(selfHostFunc)", "isJavaFunction(otherHostFunc)"})
|
||||||
boolean equalsHostFunctions(
|
boolean equalsHostFunctions(
|
||||||
|
VirtualFrame frame,
|
||||||
Object selfHostFunc,
|
Object selfHostFunc,
|
||||||
Object otherHostFunc,
|
Object otherHostFunc,
|
||||||
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop,
|
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop,
|
||||||
@Shared("equalsNode") @Cached EqualsNode equalsNode) {
|
@Shared("equalsNode") @Cached EqualsNode equalsNode) {
|
||||||
Object selfFuncStrRepr = interop.toDisplayString(selfHostFunc);
|
Object selfFuncStrRepr = interop.toDisplayString(selfHostFunc);
|
||||||
Object otherFuncStrRepr = interop.toDisplayString(otherHostFunc);
|
Object otherFuncStrRepr = interop.toDisplayString(otherHostFunc);
|
||||||
return equalsNode.execute(selfFuncStrRepr, otherFuncStrRepr);
|
return equalsNode.execute(frame, selfFuncStrRepr, otherFuncStrRepr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization(guards = "fallbackGuard(left, right, interop, warningsLib)")
|
@Specialization(guards = "fallbackGuard(left, right, interop, warningsLib)")
|
||||||
@ -419,7 +427,8 @@ public abstract class EqualsComplexNode extends Node {
|
|||||||
// all the other guards on purpose.
|
// all the other guards on purpose.
|
||||||
boolean fallbackGuard(
|
boolean fallbackGuard(
|
||||||
Object left, Object right, InteropLibrary interop, WarningsLibrary warnings) {
|
Object left, Object right, InteropLibrary interop, WarningsLibrary warnings) {
|
||||||
if (EqualsNode.isPrimitive(left, interop) && EqualsNode.isPrimitive(right, interop)) {
|
if (EqualsSimpleNode.isPrimitive(left, interop)
|
||||||
|
&& EqualsSimpleNode.isPrimitive(right, interop)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (isJavaObject(left) && isJavaObject(right)) {
|
if (isJavaObject(left) && isJavaObject(right)) {
|
||||||
|
@ -1,27 +1,29 @@
|
|||||||
package org.enso.interpreter.node.expression.builtin.meta;
|
package org.enso.interpreter.node.expression.builtin.meta;
|
||||||
|
|
||||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
import com.oracle.truffle.api.CompilerDirectives;
|
||||||
import com.oracle.truffle.api.dsl.Cached;
|
import com.oracle.truffle.api.dsl.Cached;
|
||||||
import com.oracle.truffle.api.dsl.Cached.Shared;
|
import com.oracle.truffle.api.dsl.Cached.Shared;
|
||||||
import com.oracle.truffle.api.dsl.GenerateUncached;
|
import com.oracle.truffle.api.dsl.GenerateUncached;
|
||||||
|
import com.oracle.truffle.api.dsl.NeverDefault;
|
||||||
import com.oracle.truffle.api.dsl.Specialization;
|
import com.oracle.truffle.api.dsl.Specialization;
|
||||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
import com.oracle.truffle.api.interop.TruffleObject;
|
import com.oracle.truffle.api.interop.ArityException;
|
||||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
|
||||||
import com.oracle.truffle.api.library.CachedLibrary;
|
|
||||||
import com.oracle.truffle.api.nodes.Node;
|
import com.oracle.truffle.api.nodes.Node;
|
||||||
import java.math.BigInteger;
|
|
||||||
import org.enso.interpreter.dsl.AcceptsError;
|
import org.enso.interpreter.dsl.AcceptsError;
|
||||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||||
import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps;
|
import org.enso.interpreter.node.EnsoRootNode;
|
||||||
|
import org.enso.interpreter.node.callable.InteropConversionCallNode;
|
||||||
|
import org.enso.interpreter.node.callable.InvokeCallableNode.ArgumentsExecutionMode;
|
||||||
|
import org.enso.interpreter.node.callable.InvokeCallableNode.DefaultsExecutionMode;
|
||||||
|
import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode;
|
||||||
import org.enso.interpreter.runtime.EnsoContext;
|
import org.enso.interpreter.runtime.EnsoContext;
|
||||||
import org.enso.interpreter.runtime.data.EnsoMultiValue;
|
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
|
||||||
import org.enso.interpreter.runtime.data.atom.Atom;
|
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
|
||||||
import org.enso.interpreter.runtime.data.atom.AtomConstructor;
|
import org.enso.interpreter.runtime.callable.function.Function;
|
||||||
import org.enso.interpreter.runtime.data.text.Text;
|
import org.enso.interpreter.runtime.data.Type;
|
||||||
import org.enso.interpreter.runtime.error.WarningsLibrary;
|
import org.enso.interpreter.runtime.error.PanicException;
|
||||||
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
import org.enso.interpreter.runtime.scope.ModuleScope;
|
||||||
import org.enso.polyglot.common_utils.Core_Text_Utils;
|
import org.enso.interpreter.runtime.state.State;
|
||||||
|
|
||||||
@BuiltinMethod(
|
@BuiltinMethod(
|
||||||
type = "Any",
|
type = "Any",
|
||||||
@ -40,367 +42,252 @@ import org.enso.polyglot.common_utils.Core_Text_Utils;
|
|||||||
references point to the same object on the heap. Moreover, `Meta.is_same_object`
|
references point to the same object on the heap. Moreover, `Meta.is_same_object`
|
||||||
implies `Any.==` for all object with the exception of `Number.nan`.
|
implies `Any.==` for all object with the exception of `Number.nan`.
|
||||||
""")
|
""")
|
||||||
@GenerateUncached
|
public final class EqualsNode extends Node {
|
||||||
public abstract class EqualsNode extends Node {
|
@Child private EqualsSimpleNode node;
|
||||||
|
@Child private TypeOfNode types;
|
||||||
|
@Child private WithConversionNode convert;
|
||||||
|
|
||||||
public static EqualsNode build() {
|
private static final EqualsNode UNCACHED =
|
||||||
return EqualsNodeGen.create();
|
new EqualsNode(EqualsSimpleNodeGen.getUncached(), TypeOfNode.getUncached(), true);
|
||||||
}
|
|
||||||
|
|
||||||
public abstract boolean execute(@AcceptsError Object self, @AcceptsError Object right);
|
private EqualsNode(EqualsSimpleNode node, TypeOfNode types, boolean uncached) {
|
||||||
|
this.node = node;
|
||||||
@Specialization
|
this.types = types;
|
||||||
boolean equalsBoolBool(boolean self, boolean other) {
|
if (uncached) {
|
||||||
return self == other;
|
convert = EqualsNodeFactory.WithConversionNodeGen.getUncached();
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsBoolDouble(boolean self, double other) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsBoolLong(boolean self, long other) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsBoolBigInt(boolean self, EnsoBigInteger other) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsBoolText(boolean self, Text other) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsBoolInterop(
|
|
||||||
boolean self,
|
|
||||||
Object other,
|
|
||||||
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop) {
|
|
||||||
try {
|
|
||||||
return self == interop.asBoolean(other);
|
|
||||||
} catch (UnsupportedMessageException ex) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization
|
@NeverDefault
|
||||||
boolean equalsLongLong(long self, long other) {
|
static EqualsNode build() {
|
||||||
return self == other;
|
return create();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization
|
@NeverDefault
|
||||||
boolean equalsLongBool(long self, boolean other) {
|
public static EqualsNode create() {
|
||||||
return false;
|
return new EqualsNode(EqualsSimpleNode.build(), TypeOfNode.build(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization
|
@NeverDefault
|
||||||
boolean equalsLongDouble(long self, double other) {
|
public static EqualsNode getUncached() {
|
||||||
return (double) self == other;
|
return UNCACHED;
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsLongText(long self, Text other) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
@TruffleBoundary
|
|
||||||
boolean equalsLongBigInt(long self, EnsoBigInteger other) {
|
|
||||||
if (BigIntegerOps.fitsInLong(other.getValue())) {
|
|
||||||
return BigInteger.valueOf(self).compareTo(other.getValue()) == 0;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsLongInterop(
|
|
||||||
long self,
|
|
||||||
Object other,
|
|
||||||
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop) {
|
|
||||||
try {
|
|
||||||
return self == interop.asLong(other);
|
|
||||||
} catch (UnsupportedMessageException ex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsDoubleDouble(double self, double other) {
|
|
||||||
if (Double.isNaN(self) || Double.isNaN(other)) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return self == other;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsDoubleLong(double self, long other) {
|
|
||||||
return self == (double) other;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsDoubleBool(double self, boolean other) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsDoubleText(double self, Text other) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsDoubleInterop(
|
|
||||||
double self,
|
|
||||||
Object other,
|
|
||||||
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop) {
|
|
||||||
try {
|
|
||||||
if (interop.fitsInDouble(other)) {
|
|
||||||
return self == interop.asDouble(other);
|
|
||||||
}
|
|
||||||
var otherBig = interop.asBigInteger(other);
|
|
||||||
return self == asDouble(otherBig);
|
|
||||||
} catch (UnsupportedMessageException ex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TruffleBoundary
|
|
||||||
private static double asDouble(BigInteger big) {
|
|
||||||
return big.doubleValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization(guards = {"isBigInteger(iop, self)", "isBigInteger(iop, other)"})
|
|
||||||
@TruffleBoundary
|
|
||||||
boolean other(
|
|
||||||
Object self,
|
|
||||||
Object other,
|
|
||||||
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
|
||||||
return asBigInteger(iop, self).equals(asBigInteger(iop, other));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization(guards = "isBigInteger(iop, self)")
|
|
||||||
@TruffleBoundary
|
|
||||||
boolean equalsBigIntDouble(
|
|
||||||
Object self,
|
|
||||||
double other,
|
|
||||||
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
|
||||||
return asBigInteger(iop, self).doubleValue() == other;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization(guards = "isBigInteger(iop, self)")
|
|
||||||
@TruffleBoundary
|
|
||||||
boolean equalsBigIntLong(
|
|
||||||
Object self, long other, @Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
|
||||||
var v = asBigInteger(iop, self);
|
|
||||||
if (BigIntegerOps.fitsInLong(v)) {
|
|
||||||
return v.compareTo(BigInteger.valueOf(other)) == 0;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsBigIntBool(EnsoBigInteger self, boolean other) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsBigIntText(EnsoBigInteger self, Text other) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@TruffleBoundary
|
|
||||||
@Specialization(guards = {"isBigInteger(iop, self)", "!isPrimitiveValue(other)"})
|
|
||||||
boolean equalsBigIntInterop(
|
|
||||||
Object self,
|
|
||||||
Object other,
|
|
||||||
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
|
||||||
try {
|
|
||||||
var otherBigInteger = InteropLibrary.getUncached().asBigInteger(other);
|
|
||||||
return asBigInteger(iop, self).equals(otherBigInteger);
|
|
||||||
} catch (UnsupportedMessageException ex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization(guards = {"selfText.is_normalized()", "otherText.is_normalized()"})
|
|
||||||
boolean equalsTextText(Text selfText, Text otherText) {
|
|
||||||
return selfText.toString().compareTo(otherText.toString()) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsTextBool(Text self, boolean other) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsTextLong(Text selfText, long otherLong) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsTextDouble(Text selfText, double otherDouble) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsTextBigInt(Text self, EnsoBigInteger other) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares interop string with other object. If the other object doesn't support conversion to
|
* Compares two objects for equality. If the {@link EqualsSimpleNode simple check} fails, it tries
|
||||||
* String, it is not equal. Otherwise the two strings are compared according to the
|
* to convert first argument to the second one and compare again.
|
||||||
* lexicographical order, handling Unicode normalization. See {@code Text_Utils.compare_to}.
|
*
|
||||||
|
* @param frame the stack frame we are executing at
|
||||||
|
* @param self the self object
|
||||||
|
* @param other the other object
|
||||||
|
* @return {@code true} if {@code self} and {@code that} seem equal
|
||||||
*/
|
*/
|
||||||
@TruffleBoundary
|
public boolean execute(
|
||||||
|
VirtualFrame frame, @AcceptsError Object self, @AcceptsError Object other) {
|
||||||
|
var areEqual = node.execute(frame, self, other);
|
||||||
|
if (!areEqual) {
|
||||||
|
var selfType = types.execute(self);
|
||||||
|
var otherType = types.execute(other);
|
||||||
|
if (selfType != otherType) {
|
||||||
|
if (convert == null) {
|
||||||
|
CompilerDirectives.transferToInterpreter();
|
||||||
|
convert = insert(WithConversionNode.create());
|
||||||
|
}
|
||||||
|
return convert.executeWithConversion(frame, other, self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return areEqual;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A node that checks the type of {@code that} argument, performs conversion of {@code self} to
|
||||||
|
* {@code that} type, and executes equality check again.
|
||||||
|
*/
|
||||||
|
@GenerateUncached
|
||||||
|
abstract static class WithConversionNode extends Node {
|
||||||
|
|
||||||
|
@NeverDefault
|
||||||
|
static WithConversionNode create() {
|
||||||
|
return EqualsNodeFactory.WithConversionNodeGen.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {code false} if the conversion makes no sense or result of equality check after doing
|
||||||
|
* the conversion
|
||||||
|
*/
|
||||||
|
abstract boolean executeWithConversion(VirtualFrame frame, Object self, Object that);
|
||||||
|
|
||||||
|
static Type findType(TypeOfNode typeOfNode, Object obj) {
|
||||||
|
var rawType = typeOfNode.execute(obj);
|
||||||
|
return rawType instanceof Type type ? type : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Type findTypeUncached(Object obj) {
|
||||||
|
return findType(TypeOfNode.getUncached(), obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isDefinedIn(ModuleScope scope, Function fn) {
|
||||||
|
if (fn.getCallTarget().getRootNode() instanceof EnsoRootNode ensoRoot) {
|
||||||
|
return ensoRoot.getModuleScope() == scope;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CompilerDirectives.TruffleBoundary
|
||||||
|
private static Object convertor(EnsoContext ctx, Function convFn, Object value) {
|
||||||
|
var argSchema = new CallArgumentInfo[] {new CallArgumentInfo(), new CallArgumentInfo()};
|
||||||
|
var node =
|
||||||
|
InvokeFunctionNode.build(
|
||||||
|
argSchema, DefaultsExecutionMode.EXECUTE, ArgumentsExecutionMode.EXECUTE);
|
||||||
|
var state = State.create(ctx);
|
||||||
|
return node.execute(
|
||||||
|
convFn, null, state, new Object[] {ctx.getBuiltins().comparable(), value});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code null} if no conversion found
|
||||||
|
*/
|
||||||
|
Boolean findConversions(Type selfType, Type thatType, Object self, Object that) {
|
||||||
|
if (selfType == null || thatType == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var ctx = EnsoContext.get(this);
|
||||||
|
|
||||||
|
if (findConversionImpl(ctx, selfType, thatType, self, that)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
if (findConversionImpl(ctx, thatType, selfType, that, self)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean findConversionImpl(
|
||||||
|
EnsoContext ctx, Type selfType, Type thatType, Object self, Object that) {
|
||||||
|
var selfScope = selfType.getDefinitionScope();
|
||||||
|
var comparableType = ctx.getBuiltins().comparable().getType();
|
||||||
|
|
||||||
|
var fromSelfType =
|
||||||
|
UnresolvedConversion.build(selfScope).resolveFor(ctx, comparableType, selfType);
|
||||||
|
var fromThatType =
|
||||||
|
UnresolvedConversion.build(selfScope).resolveFor(ctx, comparableType, thatType);
|
||||||
|
var betweenBoth = UnresolvedConversion.build(selfScope).resolveFor(ctx, selfType, thatType);
|
||||||
|
|
||||||
|
if (isDefinedIn(selfScope, fromSelfType)
|
||||||
|
&& isDefinedIn(selfScope, fromThatType)
|
||||||
|
&& convertor(ctx, fromSelfType, self) == convertor(ctx, fromThatType, that)
|
||||||
|
&& betweenBoth != null) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Specialization(
|
@Specialization(
|
||||||
guards = {"selfInterop.isString(selfString)"},
|
limit = "10",
|
||||||
limit = "3")
|
guards = {
|
||||||
boolean equalsStrings(
|
"selfType != null",
|
||||||
Object selfString,
|
"thatType != null",
|
||||||
Object otherString,
|
"selfType == findType(typeOfNode, self)",
|
||||||
@CachedLibrary("selfString") InteropLibrary selfInterop,
|
"thatType == findType(typeOfNode, that)"
|
||||||
@CachedLibrary("otherString") InteropLibrary otherInterop) {
|
})
|
||||||
String selfJavaString;
|
final boolean doConversionCached(
|
||||||
String otherJavaString;
|
VirtualFrame frame,
|
||||||
try {
|
|
||||||
selfJavaString = selfInterop.asString(selfString);
|
|
||||||
otherJavaString = otherInterop.asString(otherString);
|
|
||||||
} catch (UnsupportedMessageException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return Core_Text_Utils.equals(selfJavaString, otherJavaString);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization(guards = "isPrimitive(self, interop) != isPrimitive(other, interop)")
|
|
||||||
boolean equalsDifferent(
|
|
||||||
Object self,
|
Object self,
|
||||||
Object other,
|
Object that,
|
||||||
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop) {
|
@Shared("typeOf") @Cached TypeOfNode typeOfNode,
|
||||||
|
@Cached(value = "findType(typeOfNode, self)", uncached = "findTypeUncached(self)")
|
||||||
|
Type selfType,
|
||||||
|
@Cached(value = "findType(typeOfNode, that)", uncached = "findTypeUncached(that)")
|
||||||
|
Type thatType,
|
||||||
|
@Cached("findConversions(selfType, thatType, self, that)") Boolean convert,
|
||||||
|
@Shared("convert") @Cached InteropConversionCallNode convertNode,
|
||||||
|
@Shared("invoke") @Cached(allowUncached = true) EqualsSimpleNode equalityNode) {
|
||||||
|
if (convert == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (convert) {
|
||||||
/** Equals for Atoms and AtomConstructors */
|
return doDispatch(frame, that, self, thatType, convertNode, equalityNode);
|
||||||
@Specialization
|
} else {
|
||||||
boolean equalsAtomConstructors(AtomConstructor self, AtomConstructor other) {
|
return doDispatch(frame, self, that, selfType, convertNode, equalityNode);
|
||||||
return self == other;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Specialization
|
@Specialization(replaces = "doConversionCached")
|
||||||
boolean equalsAtoms(
|
final boolean doConversionUncached(
|
||||||
Atom self,
|
VirtualFrame frame,
|
||||||
Atom other,
|
|
||||||
@Cached EqualsAtomNode equalsAtomNode,
|
|
||||||
@Shared("isSameObjectNode") @Cached IsSameObjectNode isSameObjectNode) {
|
|
||||||
return isSameObjectNode.execute(self, other) || equalsAtomNode.execute(self, other);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsReverseBoolean(
|
|
||||||
TruffleObject self,
|
|
||||||
boolean other,
|
|
||||||
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop,
|
|
||||||
@Shared("reverse") @Cached EqualsNode reverse) {
|
|
||||||
return reverse.execute(other, self);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsReverseLong(
|
|
||||||
TruffleObject self,
|
|
||||||
long other,
|
|
||||||
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop,
|
|
||||||
@Shared("reverse") @Cached EqualsNode reverse) {
|
|
||||||
return reverse.execute(other, self);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsReverseDouble(
|
|
||||||
TruffleObject self,
|
|
||||||
double other,
|
|
||||||
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop,
|
|
||||||
@Shared("reverse") @Cached EqualsNode reverse) {
|
|
||||||
return reverse.execute(other, self);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization
|
|
||||||
boolean equalsReverseBigInt(
|
|
||||||
TruffleObject self,
|
|
||||||
EnsoBigInteger other,
|
|
||||||
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop,
|
|
||||||
@Shared("reverse") @Cached EqualsNode reverse) {
|
|
||||||
return reverse.execute(other, self);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Specialization(guards = "isNotPrimitive(self, other, interop, warnings)")
|
|
||||||
boolean equalsComplex(
|
|
||||||
Object self,
|
Object self,
|
||||||
Object other,
|
Object that,
|
||||||
@Cached EqualsComplexNode equalsComplex,
|
@Shared("typeOf") @Cached TypeOfNode typeOfNode,
|
||||||
@Shared("isSameObjectNode") @Cached IsSameObjectNode isSameObjectNode,
|
@Shared("convert") @Cached InteropConversionCallNode convertNode,
|
||||||
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop,
|
@Shared("invoke") @Cached(allowUncached = true) EqualsSimpleNode equalityNode) {
|
||||||
@CachedLibrary(limit = "5") WarningsLibrary warnings) {
|
var selfType = findType(typeOfNode, self);
|
||||||
return isSameObjectNode.execute(self, other) || equalsComplex.execute(self, other);
|
var thatType = findType(typeOfNode, that);
|
||||||
|
var conv = findConversions(selfType, thatType, self, that);
|
||||||
|
if (conv != null) {
|
||||||
|
var result =
|
||||||
|
conv
|
||||||
|
? doDispatch(frame, that, self, thatType, convertNode, equalityNode)
|
||||||
|
: doDispatch(frame, self, that, selfType, convertNode, equalityNode);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean isNotPrimitive(
|
|
||||||
Object a, Object b, InteropLibrary interop, WarningsLibrary warnings) {
|
|
||||||
if (a instanceof AtomConstructor && b instanceof AtomConstructor) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (a instanceof Atom && b instanceof Atom) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (warnings.hasWarnings(a) || warnings.hasWarnings(b)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (a instanceof EnsoMultiValue || b instanceof EnsoMultiValue) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return !isPrimitive(a, interop) && !isPrimitive(b, interop);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
private boolean doDispatch(
|
||||||
* Return true iff object is a primitive value used in some specializations guard. By primitive
|
VirtualFrame frame,
|
||||||
* value we mean any value that can be present in Enso, so, for example, not Integer, as that
|
Object self,
|
||||||
* cannot be present in Enso. All the primitive types should be handled in their corresponding
|
Object that,
|
||||||
* specializations. See {@link
|
Type selfType,
|
||||||
* org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode}.
|
InteropConversionCallNode convertNode,
|
||||||
*/
|
EqualsSimpleNode equalityNode)
|
||||||
static boolean isPrimitive(Object object, InteropLibrary interop) {
|
throws PanicException {
|
||||||
return isPrimitiveValue(object)
|
var convert = UnresolvedConversion.build(selfType.getDefinitionScope());
|
||||||
|| object instanceof EnsoBigInteger
|
|
||||||
|| object instanceof Text
|
|
||||||
|| interop.isString(object)
|
|
||||||
|| interop.isNumber(object)
|
|
||||||
|| interop.isBoolean(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
static boolean isPrimitiveValue(Object object) {
|
var ctx = EnsoContext.get(this);
|
||||||
return object instanceof Boolean || object instanceof Long || object instanceof Double;
|
var state = State.create(ctx);
|
||||||
}
|
|
||||||
|
|
||||||
static boolean isBigInteger(InteropLibrary iop, Object v) {
|
|
||||||
if (v instanceof EnsoBigInteger) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return !iop.fitsInDouble(v) && !iop.fitsInLong(v) && iop.fitsInBigInteger(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BigInteger asBigInteger(InteropLibrary iop, Object v) {
|
|
||||||
if (v instanceof EnsoBigInteger big) {
|
|
||||||
return big.getValue();
|
|
||||||
} else {
|
|
||||||
try {
|
try {
|
||||||
return iop.asBigInteger(v);
|
var thatAsSelf = convertNode.execute(convert, state, new Object[] {selfType, that});
|
||||||
} catch (UnsupportedMessageException ex) {
|
var result = equalityNode.execute(frame, self, thatAsSelf);
|
||||||
throw EnsoContext.get(this).raiseAssertionPanic(this, "Expecting BigInteger", ex);
|
assert !result || assertHashCodeIsTheSame(that, thatAsSelf);
|
||||||
|
return result;
|
||||||
|
} catch (ArityException ex) {
|
||||||
|
var assertsOn = false;
|
||||||
|
assert assertsOn = true;
|
||||||
|
if (assertsOn) {
|
||||||
|
throw new AssertionError("Unexpected arity exception", ex);
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
} catch (PanicException ex) {
|
||||||
|
if (ctx.getBuiltins().error().isNoSuchConversionError(ex.getPayload())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean assertHashCodeIsTheSame(Object self, Object converted) {
|
||||||
|
var selfHash = HashCodeNode.getUncached().execute(self);
|
||||||
|
var convertedHash = HashCodeNode.getUncached().execute(converted);
|
||||||
|
var ok = selfHash == convertedHash;
|
||||||
|
if (!ok) {
|
||||||
|
var msg =
|
||||||
|
"Different hash code! Original "
|
||||||
|
+ self
|
||||||
|
+ "[#"
|
||||||
|
+ Long.toHexString(selfHash)
|
||||||
|
+ "] got converted to "
|
||||||
|
+ converted
|
||||||
|
+ "[#"
|
||||||
|
+ Long.toHexString(convertedHash)
|
||||||
|
+ "]";
|
||||||
|
var ctx = EnsoContext.get(this);
|
||||||
|
throw ctx.raiseAssertionPanic(this, msg, new AssertionError(msg));
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,386 @@
|
|||||||
|
package org.enso.interpreter.node.expression.builtin.meta;
|
||||||
|
|
||||||
|
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||||
|
import com.oracle.truffle.api.dsl.Cached;
|
||||||
|
import com.oracle.truffle.api.dsl.Cached.Shared;
|
||||||
|
import com.oracle.truffle.api.dsl.GenerateUncached;
|
||||||
|
import com.oracle.truffle.api.dsl.Specialization;
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
|
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||||
|
import com.oracle.truffle.api.interop.TruffleObject;
|
||||||
|
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||||
|
import com.oracle.truffle.api.library.CachedLibrary;
|
||||||
|
import com.oracle.truffle.api.nodes.Node;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps;
|
||||||
|
import org.enso.interpreter.runtime.EnsoContext;
|
||||||
|
import org.enso.interpreter.runtime.data.EnsoMultiValue;
|
||||||
|
import org.enso.interpreter.runtime.data.atom.Atom;
|
||||||
|
import org.enso.interpreter.runtime.data.atom.AtomConstructor;
|
||||||
|
import org.enso.interpreter.runtime.data.text.Text;
|
||||||
|
import org.enso.interpreter.runtime.error.WarningsLibrary;
|
||||||
|
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||||
|
import org.enso.polyglot.common_utils.Core_Text_Utils;
|
||||||
|
|
||||||
|
@GenerateUncached
|
||||||
|
public abstract class EqualsSimpleNode extends Node {
|
||||||
|
|
||||||
|
public static EqualsSimpleNode build() {
|
||||||
|
return EqualsSimpleNodeGen.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract boolean execute(VirtualFrame frame, Object self, Object right);
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsBoolBool(boolean self, boolean other) {
|
||||||
|
return self == other;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsBoolDouble(boolean self, double other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsBoolLong(boolean self, long other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsBoolBigInt(boolean self, EnsoBigInteger other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsBoolText(boolean self, Text other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsBoolInterop(
|
||||||
|
boolean self,
|
||||||
|
Object other,
|
||||||
|
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop) {
|
||||||
|
try {
|
||||||
|
return self == interop.asBoolean(other);
|
||||||
|
} catch (UnsupportedMessageException ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsInteropBool(
|
||||||
|
TruffleObject self,
|
||||||
|
boolean other,
|
||||||
|
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop) {
|
||||||
|
try {
|
||||||
|
return other == interop.asBoolean(self);
|
||||||
|
} catch (UnsupportedMessageException ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsLongLong(long self, long other) {
|
||||||
|
return self == other;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsLongBool(long self, boolean other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsLongDouble(long self, double other) {
|
||||||
|
return (double) self == other;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsLongText(long self, Text other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
@TruffleBoundary
|
||||||
|
boolean equalsLongBigInt(long self, EnsoBigInteger other) {
|
||||||
|
if (BigIntegerOps.fitsInLong(other.getValue())) {
|
||||||
|
return BigInteger.valueOf(self).compareTo(other.getValue()) == 0;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsLongInterop(
|
||||||
|
long self,
|
||||||
|
Object other,
|
||||||
|
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop) {
|
||||||
|
try {
|
||||||
|
return self == interop.asLong(other);
|
||||||
|
} catch (UnsupportedMessageException ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsDoubleDouble(double self, double other) {
|
||||||
|
if (Double.isNaN(self) || Double.isNaN(other)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return self == other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsDoubleLong(double self, long other) {
|
||||||
|
return self == (double) other;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsDoubleBool(double self, boolean other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsDoubleText(double self, Text other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsDoubleInterop(
|
||||||
|
double self,
|
||||||
|
Object other,
|
||||||
|
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop) {
|
||||||
|
try {
|
||||||
|
if (interop.fitsInDouble(other)) {
|
||||||
|
return self == interop.asDouble(other);
|
||||||
|
}
|
||||||
|
var otherBig = interop.asBigInteger(other);
|
||||||
|
return self == asDouble(otherBig);
|
||||||
|
} catch (UnsupportedMessageException ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TruffleBoundary
|
||||||
|
private static double asDouble(BigInteger big) {
|
||||||
|
return big.doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization(guards = {"isBigInteger(iop, self)", "isBigInteger(iop, other)"})
|
||||||
|
@TruffleBoundary
|
||||||
|
boolean other(
|
||||||
|
Object self,
|
||||||
|
Object other,
|
||||||
|
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||||
|
return asBigInteger(iop, self).equals(asBigInteger(iop, other));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization(guards = "isBigInteger(iop, self)")
|
||||||
|
@TruffleBoundary
|
||||||
|
boolean equalsBigIntDouble(
|
||||||
|
Object self,
|
||||||
|
double other,
|
||||||
|
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||||
|
return asBigInteger(iop, self).doubleValue() == other;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization(guards = "isBigInteger(iop, self)")
|
||||||
|
@TruffleBoundary
|
||||||
|
boolean equalsBigIntLong(
|
||||||
|
Object self, long other, @Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||||
|
var v = asBigInteger(iop, self);
|
||||||
|
if (BigIntegerOps.fitsInLong(v)) {
|
||||||
|
return v.compareTo(BigInteger.valueOf(other)) == 0;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsBigIntText(EnsoBigInteger self, Text other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@TruffleBoundary
|
||||||
|
@Specialization(guards = {"isBigInteger(iop, self)", "!isPrimitiveValue(other)"})
|
||||||
|
boolean equalsBigIntInterop(
|
||||||
|
Object self,
|
||||||
|
Object other,
|
||||||
|
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
|
||||||
|
try {
|
||||||
|
var otherBigInteger = InteropLibrary.getUncached().asBigInteger(other);
|
||||||
|
return asBigInteger(iop, self).equals(otherBigInteger);
|
||||||
|
} catch (UnsupportedMessageException ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization(guards = {"selfText.is_normalized()", "otherText.is_normalized()"})
|
||||||
|
boolean equalsTextText(Text selfText, Text otherText) {
|
||||||
|
return selfText.toString().compareTo(otherText.toString()) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsTextLong(Text selfText, long otherLong) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsTextDouble(Text selfText, double otherDouble) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsTextBigInt(Text self, EnsoBigInteger other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares interop string with other object. If the other object doesn't support conversion to
|
||||||
|
* String, it is not equal. Otherwise the two strings are compared according to the
|
||||||
|
* lexicographical order, handling Unicode normalization. See {@code Text_Utils.compare_to}.
|
||||||
|
*/
|
||||||
|
@TruffleBoundary
|
||||||
|
@Specialization(
|
||||||
|
guards = {"selfInterop.isString(selfString)"},
|
||||||
|
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) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return Core_Text_Utils.equals(selfJavaString, otherJavaString);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization(guards = "isPrimitive(self, interop) != isPrimitive(other, interop)")
|
||||||
|
boolean equalsDifferent(
|
||||||
|
Object self,
|
||||||
|
Object other,
|
||||||
|
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Equals for Atoms and AtomConstructors */
|
||||||
|
@Specialization
|
||||||
|
boolean equalsAtomConstructors(AtomConstructor self, AtomConstructor other) {
|
||||||
|
return self == other;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsAtoms(
|
||||||
|
VirtualFrame frame,
|
||||||
|
Atom self,
|
||||||
|
Atom other,
|
||||||
|
@Cached EqualsAtomNode equalsAtomNode,
|
||||||
|
@Shared("isSameObjectNode") @Cached IsSameObjectNode isSameObjectNode) {
|
||||||
|
return isSameObjectNode.execute(self, other) || equalsAtomNode.execute(frame, self, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsReverseLong(
|
||||||
|
VirtualFrame frame,
|
||||||
|
TruffleObject self,
|
||||||
|
long other,
|
||||||
|
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop,
|
||||||
|
@Shared("reverse") @Cached EqualsSimpleNode reverse) {
|
||||||
|
return reverse.execute(frame, other, self);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsReverseDouble(
|
||||||
|
VirtualFrame frame,
|
||||||
|
TruffleObject self,
|
||||||
|
double other,
|
||||||
|
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop,
|
||||||
|
@Shared("reverse") @Cached EqualsSimpleNode reverse) {
|
||||||
|
return reverse.execute(frame, other, self);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization
|
||||||
|
boolean equalsReverseBigInt(
|
||||||
|
VirtualFrame frame,
|
||||||
|
TruffleObject self,
|
||||||
|
EnsoBigInteger other,
|
||||||
|
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop,
|
||||||
|
@Shared("reverse") @Cached EqualsSimpleNode reverse) {
|
||||||
|
return reverse.execute(frame, other, self);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Specialization(guards = "isNotPrimitive(self, other, interop, warnings)")
|
||||||
|
boolean equalsComplex(
|
||||||
|
VirtualFrame frame,
|
||||||
|
Object self,
|
||||||
|
Object other,
|
||||||
|
@Cached EqualsComplexNode equalsComplex,
|
||||||
|
@Shared("isSameObjectNode") @Cached IsSameObjectNode isSameObjectNode,
|
||||||
|
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop,
|
||||||
|
@CachedLibrary(limit = "5") WarningsLibrary warnings) {
|
||||||
|
return isSameObjectNode.execute(self, other) || equalsComplex.execute(frame, self, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isNotPrimitive(
|
||||||
|
Object a, Object b, InteropLibrary interop, WarningsLibrary warnings) {
|
||||||
|
if (a instanceof AtomConstructor && b instanceof AtomConstructor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (a instanceof Atom && b instanceof Atom) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (warnings.hasWarnings(a) || warnings.hasWarnings(b)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (a instanceof EnsoMultiValue || b instanceof EnsoMultiValue) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !isPrimitive(a, interop) && !isPrimitive(b, interop);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true iff object is a primitive value used in some specializations guard. By primitive
|
||||||
|
* value we mean any value that can be present in Enso, so, for example, not Integer, as that
|
||||||
|
* cannot be present in Enso. All the primitive types should be handled in their corresponding
|
||||||
|
* specializations. See {@link
|
||||||
|
* org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode}.
|
||||||
|
*/
|
||||||
|
static boolean isPrimitive(Object object, InteropLibrary interop) {
|
||||||
|
return isPrimitiveValue(object)
|
||||||
|
|| object instanceof EnsoBigInteger
|
||||||
|
|| object instanceof Text
|
||||||
|
|| interop.isString(object)
|
||||||
|
|| interop.isNumber(object)
|
||||||
|
|| interop.isBoolean(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isPrimitiveValue(Object object) {
|
||||||
|
return object instanceof Boolean || object instanceof Long || object instanceof Double;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isBigInteger(InteropLibrary iop, Object v) {
|
||||||
|
if (v instanceof EnsoBigInteger) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return !iop.fitsInDouble(v) && !iop.fitsInLong(v) && iop.fitsInBigInteger(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BigInteger asBigInteger(InteropLibrary iop, Object v) {
|
||||||
|
if (v instanceof EnsoBigInteger big) {
|
||||||
|
return big.getValue();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return iop.asBigInteger(v);
|
||||||
|
} catch (UnsupportedMessageException ex) {
|
||||||
|
throw EnsoContext.get(this).raiseAssertionPanic(this, "Expecting BigInteger", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -83,6 +83,10 @@ public abstract class HashCodeNode extends Node {
|
|||||||
return HashCodeNodeGen.create();
|
return HashCodeNodeGen.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static HashCodeNode getUncached() {
|
||||||
|
return HashCodeNodeGen.getUncached();
|
||||||
|
}
|
||||||
|
|
||||||
public abstract long execute(@AcceptsError Object object);
|
public abstract long execute(@AcceptsError Object object);
|
||||||
|
|
||||||
/** Specializations for primitive values * */
|
/** Specializations for primitive values * */
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.enso.interpreter.node.expression.builtin.ordering;
|
package org.enso.interpreter.node.expression.builtin.ordering;
|
||||||
|
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
import com.oracle.truffle.api.nodes.Node;
|
import com.oracle.truffle.api.nodes.Node;
|
||||||
import org.enso.interpreter.dsl.AcceptsError;
|
import org.enso.interpreter.dsl.AcceptsError;
|
||||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||||
@ -11,6 +12,7 @@ public final class SortArrayNode extends Node {
|
|||||||
@Child private SortVectorNode sortVectorNode = SortVectorNode.build();
|
@Child private SortVectorNode sortVectorNode = SortVectorNode.build();
|
||||||
|
|
||||||
public Object execute(
|
public Object execute(
|
||||||
|
VirtualFrame frame,
|
||||||
State state,
|
State state,
|
||||||
@AcceptsError Object self,
|
@AcceptsError Object self,
|
||||||
long ascending,
|
long ascending,
|
||||||
@ -20,7 +22,15 @@ public final class SortArrayNode extends Node {
|
|||||||
Object onFunc,
|
Object onFunc,
|
||||||
long problemBehavior) {
|
long problemBehavior) {
|
||||||
return sortVectorNode.execute(
|
return sortVectorNode.execute(
|
||||||
state, self, ascending, comparators, compareFunctions, byFunc, onFunc, problemBehavior);
|
frame,
|
||||||
|
state,
|
||||||
|
self,
|
||||||
|
ascending,
|
||||||
|
comparators,
|
||||||
|
compareFunctions,
|
||||||
|
byFunc,
|
||||||
|
onFunc,
|
||||||
|
problemBehavior);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SortArrayNode build() {
|
public static SortArrayNode build() {
|
||||||
|
@ -6,6 +6,8 @@ import com.oracle.truffle.api.dsl.Cached;
|
|||||||
import com.oracle.truffle.api.dsl.Cached.Shared;
|
import com.oracle.truffle.api.dsl.Cached.Shared;
|
||||||
import com.oracle.truffle.api.dsl.GenerateUncached;
|
import com.oracle.truffle.api.dsl.GenerateUncached;
|
||||||
import com.oracle.truffle.api.dsl.Specialization;
|
import com.oracle.truffle.api.dsl.Specialization;
|
||||||
|
import com.oracle.truffle.api.frame.MaterializedFrame;
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||||
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
|
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
|
||||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||||
@ -77,6 +79,7 @@ public abstract class SortVectorNode extends Node {
|
|||||||
* @return A new, sorted vector.
|
* @return A new, sorted vector.
|
||||||
*/
|
*/
|
||||||
public abstract Object execute(
|
public abstract Object execute(
|
||||||
|
VirtualFrame frame,
|
||||||
State state,
|
State state,
|
||||||
@AcceptsError Object self,
|
@AcceptsError Object self,
|
||||||
long ascending,
|
long ascending,
|
||||||
@ -101,6 +104,7 @@ public abstract class SortVectorNode extends Node {
|
|||||||
"interop.isNull(onFunc)"
|
"interop.isNull(onFunc)"
|
||||||
})
|
})
|
||||||
Object sortPrimitives(
|
Object sortPrimitives(
|
||||||
|
VirtualFrame frame,
|
||||||
State state,
|
State state,
|
||||||
Object self,
|
Object self,
|
||||||
long ascending,
|
long ascending,
|
||||||
@ -131,7 +135,14 @@ public abstract class SortVectorNode extends Node {
|
|||||||
}
|
}
|
||||||
var javaComparator =
|
var javaComparator =
|
||||||
createDefaultComparator(
|
createDefaultComparator(
|
||||||
lessThanNode, equalsNode, typeOfNode, toTextNode, ascending, problemBehavior, interop);
|
frame.materialize(),
|
||||||
|
lessThanNode,
|
||||||
|
equalsNode,
|
||||||
|
typeOfNode,
|
||||||
|
toTextNode,
|
||||||
|
ascending,
|
||||||
|
problemBehavior,
|
||||||
|
interop);
|
||||||
try {
|
try {
|
||||||
return sortPrimitiveVector(elems, javaComparator);
|
return sortPrimitiveVector(elems, javaComparator);
|
||||||
} catch (CompareException e) {
|
} catch (CompareException e) {
|
||||||
@ -142,6 +153,7 @@ public abstract class SortVectorNode extends Node {
|
|||||||
|
|
||||||
@TruffleBoundary
|
@TruffleBoundary
|
||||||
private DefaultSortComparator createDefaultComparator(
|
private DefaultSortComparator createDefaultComparator(
|
||||||
|
MaterializedFrame frame,
|
||||||
LessThanNode lessThanNode,
|
LessThanNode lessThanNode,
|
||||||
EqualsNode equalsNode,
|
EqualsNode equalsNode,
|
||||||
TypeOfNode typeOfNode,
|
TypeOfNode typeOfNode,
|
||||||
@ -150,6 +162,7 @@ public abstract class SortVectorNode extends Node {
|
|||||||
long problemBehaviorNum,
|
long problemBehaviorNum,
|
||||||
InteropLibrary interop) {
|
InteropLibrary interop) {
|
||||||
return new DefaultSortComparator(
|
return new DefaultSortComparator(
|
||||||
|
frame,
|
||||||
lessThanNode,
|
lessThanNode,
|
||||||
equalsNode,
|
equalsNode,
|
||||||
typeOfNode,
|
typeOfNode,
|
||||||
@ -165,6 +178,7 @@ public abstract class SortVectorNode extends Node {
|
|||||||
"interop.hasArrayElements(self)",
|
"interop.hasArrayElements(self)",
|
||||||
})
|
})
|
||||||
Object sortGeneric(
|
Object sortGeneric(
|
||||||
|
MaterializedFrame frame,
|
||||||
State state,
|
State state,
|
||||||
Object self,
|
Object self,
|
||||||
long ascending,
|
long ascending,
|
||||||
@ -206,6 +220,7 @@ public abstract class SortVectorNode extends Node {
|
|||||||
if (interop.isNull(byFunc) && interop.isNull(onFunc) && isPrimitiveGroup(group)) {
|
if (interop.isNull(byFunc) && interop.isNull(onFunc) && isPrimitiveGroup(group)) {
|
||||||
javaComparator =
|
javaComparator =
|
||||||
new DefaultSortComparator(
|
new DefaultSortComparator(
|
||||||
|
frame,
|
||||||
lessThanNode,
|
lessThanNode,
|
||||||
equalsNode,
|
equalsNode,
|
||||||
typeOfNode,
|
typeOfNode,
|
||||||
@ -557,12 +572,14 @@ public abstract class SortVectorNode extends Node {
|
|||||||
*/
|
*/
|
||||||
final class DefaultSortComparator extends SortComparator {
|
final class DefaultSortComparator extends SortComparator {
|
||||||
|
|
||||||
|
private final MaterializedFrame frame;
|
||||||
private final LessThanNode lessThanNode;
|
private final LessThanNode lessThanNode;
|
||||||
private final EqualsNode equalsNode;
|
private final EqualsNode equalsNode;
|
||||||
private final TypeOfNode typeOfNode;
|
private final TypeOfNode typeOfNode;
|
||||||
private final boolean ascending;
|
private final boolean ascending;
|
||||||
|
|
||||||
private DefaultSortComparator(
|
private DefaultSortComparator(
|
||||||
|
MaterializedFrame frame,
|
||||||
LessThanNode lessThanNode,
|
LessThanNode lessThanNode,
|
||||||
EqualsNode equalsNode,
|
EqualsNode equalsNode,
|
||||||
TypeOfNode typeOfNode,
|
TypeOfNode typeOfNode,
|
||||||
@ -571,6 +588,7 @@ public abstract class SortVectorNode extends Node {
|
|||||||
ProblemBehavior problemBehavior,
|
ProblemBehavior problemBehavior,
|
||||||
InteropLibrary interop) {
|
InteropLibrary interop) {
|
||||||
super(toTextNode, problemBehavior, interop);
|
super(toTextNode, problemBehavior, interop);
|
||||||
|
this.frame = frame;
|
||||||
this.lessThanNode = lessThanNode;
|
this.lessThanNode = lessThanNode;
|
||||||
this.equalsNode = equalsNode;
|
this.equalsNode = equalsNode;
|
||||||
this.typeOfNode = typeOfNode;
|
this.typeOfNode = typeOfNode;
|
||||||
@ -583,7 +601,7 @@ public abstract class SortVectorNode extends Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int compareValuesWithDefaultComparator(Object x, Object y) {
|
int compareValuesWithDefaultComparator(Object x, Object y) {
|
||||||
if (equalsNode.execute(x, y)) {
|
if (equalsNode.execute(frame, x, y)) {
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
// Check if x < y
|
// Check if x < y
|
||||||
|
@ -369,7 +369,7 @@ public final class EnsoMultiValue implements EnsoObject {
|
|||||||
try {
|
try {
|
||||||
var members = iop.getMembers(values[i]);
|
var members = iop.getMembers(values[i]);
|
||||||
var len = iop.getArraySize(members);
|
var len = iop.getArraySize(members);
|
||||||
for (var j = 0L; j < len; i++) {
|
for (var j = 0L; j < len; j++) {
|
||||||
var name = iop.readArrayElement(members, j);
|
var name = iop.readArrayElement(members, j);
|
||||||
names.add(iop.asString(name));
|
names.add(iop.asString(name));
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package org.enso.interpreter.runtime.data.hash;
|
|||||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||||
import com.oracle.truffle.api.dsl.Cached;
|
import com.oracle.truffle.api.dsl.Cached;
|
||||||
import com.oracle.truffle.api.dsl.Cached.Shared;
|
import com.oracle.truffle.api.dsl.Cached.Shared;
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||||
import com.oracle.truffle.api.interop.UnknownKeyException;
|
import com.oracle.truffle.api.interop.UnknownKeyException;
|
||||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||||
@ -55,11 +56,11 @@ public final class EnsoHashMap implements EnsoObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EnsoHashMapBuilder getMapBuilder(
|
EnsoHashMapBuilder getMapBuilder(
|
||||||
boolean readOnly, HashCodeNode hashCodeNode, EqualsNode equalsNode) {
|
VirtualFrame frame, boolean readOnly, HashCodeNode hashCodeNode, EqualsNode equalsNode) {
|
||||||
if (readOnly) {
|
if (readOnly) {
|
||||||
return mapBuilder;
|
return mapBuilder;
|
||||||
} else {
|
} else {
|
||||||
return mapBuilder.asModifiable(generation, hashCodeNode, equalsNode);
|
return mapBuilder.asModifiable(frame, generation, hashCodeNode, equalsNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +105,7 @@ public final class EnsoHashMap implements EnsoObject {
|
|||||||
Object key,
|
Object key,
|
||||||
@Shared("hash") @Cached HashCodeNode hashCodeNode,
|
@Shared("hash") @Cached HashCodeNode hashCodeNode,
|
||||||
@Shared("equals") @Cached EqualsNode equalsNode) {
|
@Shared("equals") @Cached EqualsNode equalsNode) {
|
||||||
var entry = mapBuilder.get(key, generation, hashCodeNode, equalsNode);
|
var entry = mapBuilder.get(null, key, generation, hashCodeNode, equalsNode);
|
||||||
return entry != null;
|
return entry != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +123,7 @@ public final class EnsoHashMap implements EnsoObject {
|
|||||||
@Shared("hash") @Cached HashCodeNode hashCodeNode,
|
@Shared("hash") @Cached HashCodeNode hashCodeNode,
|
||||||
@Shared("equals") @Cached EqualsNode equalsNode)
|
@Shared("equals") @Cached EqualsNode equalsNode)
|
||||||
throws UnknownKeyException {
|
throws UnknownKeyException {
|
||||||
StorageEntry entry = mapBuilder.get(key, generation, hashCodeNode, equalsNode);
|
StorageEntry entry = mapBuilder.get(null, key, generation, hashCodeNode, equalsNode);
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
return entry.value();
|
return entry.value();
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.enso.interpreter.runtime.data.hash;
|
package org.enso.interpreter.runtime.data.hash;
|
||||||
|
|
||||||
import com.oracle.truffle.api.CompilerDirectives;
|
import com.oracle.truffle.api.CompilerDirectives;
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
|
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
|
||||||
import org.enso.interpreter.node.expression.builtin.meta.HashCodeNode;
|
import org.enso.interpreter.node.expression.builtin.meta.HashCodeNode;
|
||||||
@ -87,10 +88,10 @@ final class EnsoHashMapBuilder {
|
|||||||
* Otherwise it may return new builder suitable for additions.
|
* Otherwise it may return new builder suitable for additions.
|
||||||
*/
|
*/
|
||||||
public EnsoHashMapBuilder asModifiable(
|
public EnsoHashMapBuilder asModifiable(
|
||||||
int atGeneration, HashCodeNode hashCodeNode, EqualsNode equalsNode) {
|
VirtualFrame frame, int atGeneration, HashCodeNode hashCodeNode, EqualsNode equalsNode) {
|
||||||
if (atGeneration != generation || generation * 4 > byHash.length * 3) {
|
if (atGeneration != generation || generation * 4 > byHash.length * 3) {
|
||||||
var newSize = Math.max(actualSize * 2, byHash.length);
|
var newSize = Math.max(actualSize * 2, byHash.length);
|
||||||
return rehash(newSize, atGeneration, hashCodeNode, equalsNode);
|
return rehash(frame, newSize, atGeneration, hashCodeNode, equalsNode);
|
||||||
} else {
|
} else {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -102,7 +103,12 @@ final class EnsoHashMapBuilder {
|
|||||||
* equal key, it marks it as removed, if it hasn't been removed yet. Once it finds an empty slot,
|
* equal key, it marks it as removed, if it hasn't been removed yet. Once it finds an empty slot,
|
||||||
* it puts there a new entry with the next generation.
|
* it puts there a new entry with the next generation.
|
||||||
*/
|
*/
|
||||||
public void put(Object key, Object value, HashCodeNode hashCodeNode, EqualsNode equalsNode) {
|
public void put(
|
||||||
|
VirtualFrame frame,
|
||||||
|
Object key,
|
||||||
|
Object value,
|
||||||
|
HashCodeNode hashCodeNode,
|
||||||
|
EqualsNode equalsNode) {
|
||||||
var at = findWhereToStart(key, hashCodeNode);
|
var at = findWhereToStart(key, hashCodeNode);
|
||||||
var nextGeneration = ++generation;
|
var nextGeneration = ++generation;
|
||||||
var replacingExistingKey = false;
|
var replacingExistingKey = false;
|
||||||
@ -114,7 +120,7 @@ final class EnsoHashMapBuilder {
|
|||||||
byHash[at] = new StorageEntry(key, value, nextGeneration);
|
byHash[at] = new StorageEntry(key, value, nextGeneration);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (compare(equalsNode, byHash[at].key(), key)) {
|
if (compare(frame, equalsNode, byHash[at].key(), key)) {
|
||||||
var invalidatedEntry = byHash[at].markRemoved(nextGeneration);
|
var invalidatedEntry = byHash[at].markRemoved(nextGeneration);
|
||||||
if (invalidatedEntry != byHash[at]) {
|
if (invalidatedEntry != byHash[at]) {
|
||||||
byHash[at] = invalidatedEntry;
|
byHash[at] = invalidatedEntry;
|
||||||
@ -133,14 +139,18 @@ final class EnsoHashMapBuilder {
|
|||||||
* given {@code generation}.
|
* given {@code generation}.
|
||||||
*/
|
*/
|
||||||
public StorageEntry get(
|
public StorageEntry get(
|
||||||
Object key, int generation, HashCodeNode hashCodeNode, EqualsNode equalsNode) {
|
VirtualFrame frame,
|
||||||
|
Object key,
|
||||||
|
int generation,
|
||||||
|
HashCodeNode hashCodeNode,
|
||||||
|
EqualsNode equalsNode) {
|
||||||
var at = findWhereToStart(key, hashCodeNode);
|
var at = findWhereToStart(key, hashCodeNode);
|
||||||
for (var i = 0; i < byHash.length; i++) {
|
for (var i = 0; i < byHash.length; i++) {
|
||||||
if (byHash[at] == null) {
|
if (byHash[at] == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (byHash[at].isVisible(generation)) {
|
if (byHash[at].isVisible(generation)) {
|
||||||
if (compare(equalsNode, key, byHash[at].key())) {
|
if (compare(frame, equalsNode, key, byHash[at].key())) {
|
||||||
return byHash[at];
|
return byHash[at];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,14 +174,15 @@ final class EnsoHashMapBuilder {
|
|||||||
*
|
*
|
||||||
* @return true if the removal was successful false otherwise.
|
* @return true if the removal was successful false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean remove(Object key, HashCodeNode hashCodeNode, EqualsNode equalsNode) {
|
public boolean remove(
|
||||||
|
VirtualFrame frame, Object key, HashCodeNode hashCodeNode, EqualsNode equalsNode) {
|
||||||
var at = findWhereToStart(key, hashCodeNode);
|
var at = findWhereToStart(key, hashCodeNode);
|
||||||
var nextGeneration = ++generation;
|
var nextGeneration = ++generation;
|
||||||
for (var i = 0; i < byHash.length; i++) {
|
for (var i = 0; i < byHash.length; i++) {
|
||||||
if (byHash[at] == null) {
|
if (byHash[at] == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (compare(equalsNode, key, byHash[at].key())) {
|
if (compare(frame, equalsNode, key, byHash[at].key())) {
|
||||||
var invalidatedEntry = byHash[at].markRemoved(nextGeneration);
|
var invalidatedEntry = byHash[at].markRemoved(nextGeneration);
|
||||||
if (invalidatedEntry != byHash[at]) {
|
if (invalidatedEntry != byHash[at]) {
|
||||||
byHash[at] = invalidatedEntry;
|
byHash[at] = invalidatedEntry;
|
||||||
@ -191,12 +202,16 @@ final class EnsoHashMapBuilder {
|
|||||||
* atGeneration}.
|
* atGeneration}.
|
||||||
*/
|
*/
|
||||||
private EnsoHashMapBuilder rehash(
|
private EnsoHashMapBuilder rehash(
|
||||||
int size, int atGeneration, HashCodeNode hashCodeNode, EqualsNode equalsNode) {
|
VirtualFrame frame,
|
||||||
|
int size,
|
||||||
|
int atGeneration,
|
||||||
|
HashCodeNode hashCodeNode,
|
||||||
|
EqualsNode equalsNode) {
|
||||||
var newBuilder = new EnsoHashMapBuilder(size);
|
var newBuilder = new EnsoHashMapBuilder(size);
|
||||||
for (var i = 0; i < byHash.length; i++) {
|
for (var i = 0; i < byHash.length; i++) {
|
||||||
var entry = byHash[i];
|
var entry = byHash[i];
|
||||||
if (entry != null && entry.isVisible(atGeneration)) {
|
if (entry != null && entry.isVisible(atGeneration)) {
|
||||||
newBuilder.put(entry.key(), entry.value(), hashCodeNode, equalsNode);
|
newBuilder.put(frame, entry.key(), entry.value(), hashCodeNode, equalsNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newBuilder;
|
return newBuilder;
|
||||||
@ -224,11 +239,11 @@ final class EnsoHashMapBuilder {
|
|||||||
+ "}";
|
+ "}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean compare(EqualsNode equalsNode, Object a, Object b) {
|
private static boolean compare(VirtualFrame frame, EqualsNode equalsNode, Object a, Object b) {
|
||||||
if (a instanceof Double aDbl && b instanceof Double bDbl && aDbl.isNaN() && bDbl.isNaN()) {
|
if (a instanceof Double aDbl && b instanceof Double bDbl && aDbl.isNaN() && bDbl.isNaN()) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return equalsNode.execute(a, b);
|
return equalsNode.execute(frame, a, b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import com.oracle.truffle.api.CompilerDirectives;
|
|||||||
import com.oracle.truffle.api.dsl.Cached;
|
import com.oracle.truffle.api.dsl.Cached;
|
||||||
import com.oracle.truffle.api.dsl.Cached.Shared;
|
import com.oracle.truffle.api.dsl.Cached.Shared;
|
||||||
import com.oracle.truffle.api.dsl.Specialization;
|
import com.oracle.truffle.api.dsl.Specialization;
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||||
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
|
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
|
||||||
import com.oracle.truffle.api.interop.StopIterationException;
|
import com.oracle.truffle.api.interop.StopIterationException;
|
||||||
@ -30,17 +31,18 @@ public abstract class HashMapInsertNode extends Node {
|
|||||||
return HashMapInsertNodeGen.create();
|
return HashMapInsertNodeGen.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract EnsoHashMap execute(Object self, Object key, Object value);
|
public abstract EnsoHashMap execute(VirtualFrame frame, Object self, Object key, Object value);
|
||||||
|
|
||||||
@Specialization
|
@Specialization
|
||||||
EnsoHashMap doEnsoHashMap(
|
EnsoHashMap doEnsoHashMap(
|
||||||
|
VirtualFrame frame,
|
||||||
EnsoHashMap hashMap,
|
EnsoHashMap hashMap,
|
||||||
Object key,
|
Object key,
|
||||||
Object value,
|
Object value,
|
||||||
@Shared("hash") @Cached HashCodeNode hashCodeNode,
|
@Shared("hash") @Cached HashCodeNode hashCodeNode,
|
||||||
@Shared("equals") @Cached EqualsNode equalsNode) {
|
@Shared("equals") @Cached EqualsNode equalsNode) {
|
||||||
var mapBuilder = hashMap.getMapBuilder(false, hashCodeNode, equalsNode);
|
var mapBuilder = hashMap.getMapBuilder(frame, false, hashCodeNode, equalsNode);
|
||||||
mapBuilder.put(key, value, hashCodeNode, equalsNode);
|
mapBuilder.put(frame, key, value, hashCodeNode, equalsNode);
|
||||||
var newMap = mapBuilder.build();
|
var newMap = mapBuilder.build();
|
||||||
return newMap;
|
return newMap;
|
||||||
}
|
}
|
||||||
@ -51,6 +53,7 @@ public abstract class HashMapInsertNode extends Node {
|
|||||||
*/
|
*/
|
||||||
@Specialization(guards = "mapInterop.hasHashEntries(foreignMap)", limit = "3")
|
@Specialization(guards = "mapInterop.hasHashEntries(foreignMap)", limit = "3")
|
||||||
EnsoHashMap doForeign(
|
EnsoHashMap doForeign(
|
||||||
|
VirtualFrame frame,
|
||||||
Object foreignMap,
|
Object foreignMap,
|
||||||
Object keyToInsert,
|
Object keyToInsert,
|
||||||
Object valueToInsert,
|
Object valueToInsert,
|
||||||
@ -65,8 +68,9 @@ public abstract class HashMapInsertNode extends Node {
|
|||||||
Object keyValueArr = iteratorInterop.getIteratorNextElement(entriesIterator);
|
Object keyValueArr = iteratorInterop.getIteratorNextElement(entriesIterator);
|
||||||
Object key = iteratorInterop.readArrayElement(keyValueArr, 0);
|
Object key = iteratorInterop.readArrayElement(keyValueArr, 0);
|
||||||
Object value = iteratorInterop.readArrayElement(keyValueArr, 1);
|
Object value = iteratorInterop.readArrayElement(keyValueArr, 1);
|
||||||
mapBuilder = mapBuilder.asModifiable(mapBuilder.generation(), hashCodeNode, equalsNode);
|
mapBuilder =
|
||||||
mapBuilder.put(key, value, hashCodeNode, equalsNode);
|
mapBuilder.asModifiable(frame, mapBuilder.generation(), hashCodeNode, equalsNode);
|
||||||
|
mapBuilder.put(frame, key, value, hashCodeNode, equalsNode);
|
||||||
}
|
}
|
||||||
} catch (UnsupportedMessageException | StopIterationException | InvalidArrayIndexException e) {
|
} catch (UnsupportedMessageException | StopIterationException | InvalidArrayIndexException e) {
|
||||||
CompilerDirectives.transferToInterpreter();
|
CompilerDirectives.transferToInterpreter();
|
||||||
@ -76,8 +80,8 @@ public abstract class HashMapInsertNode extends Node {
|
|||||||
+ " has wrongly specified Interop API (hash entries iterator)";
|
+ " has wrongly specified Interop API (hash entries iterator)";
|
||||||
throw new PanicException(Text.create(msg), this);
|
throw new PanicException(Text.create(msg), this);
|
||||||
}
|
}
|
||||||
mapBuilder = mapBuilder.asModifiable(mapBuilder.generation(), hashCodeNode, equalsNode);
|
mapBuilder = mapBuilder.asModifiable(frame, mapBuilder.generation(), hashCodeNode, equalsNode);
|
||||||
mapBuilder.put(keyToInsert, valueToInsert, hashCodeNode, equalsNode);
|
mapBuilder.put(frame, keyToInsert, valueToInsert, hashCodeNode, equalsNode);
|
||||||
return EnsoHashMap.createWithBuilder(mapBuilder);
|
return EnsoHashMap.createWithBuilder(mapBuilder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import com.oracle.truffle.api.dsl.Cached;
|
|||||||
import com.oracle.truffle.api.dsl.Cached.Shared;
|
import com.oracle.truffle.api.dsl.Cached.Shared;
|
||||||
import com.oracle.truffle.api.dsl.GenerateUncached;
|
import com.oracle.truffle.api.dsl.GenerateUncached;
|
||||||
import com.oracle.truffle.api.dsl.Specialization;
|
import com.oracle.truffle.api.dsl.Specialization;
|
||||||
|
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||||
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
|
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
|
||||||
import com.oracle.truffle.api.interop.StopIterationException;
|
import com.oracle.truffle.api.interop.StopIterationException;
|
||||||
@ -29,16 +30,17 @@ public abstract class HashMapRemoveNode extends Node {
|
|||||||
return HashMapRemoveNodeGen.create();
|
return HashMapRemoveNodeGen.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract EnsoHashMap execute(Object self, Object key);
|
public abstract EnsoHashMap execute(VirtualFrame frame, Object self, Object key);
|
||||||
|
|
||||||
@Specialization
|
@Specialization
|
||||||
EnsoHashMap removeFromEnsoMap(
|
EnsoHashMap removeFromEnsoMap(
|
||||||
|
VirtualFrame frame,
|
||||||
EnsoHashMap ensoMap,
|
EnsoHashMap ensoMap,
|
||||||
Object key,
|
Object key,
|
||||||
@Shared("hash") @Cached HashCodeNode hashCodeNode,
|
@Shared("hash") @Cached HashCodeNode hashCodeNode,
|
||||||
@Shared("equals") @Cached EqualsNode equalsNode) {
|
@Shared("equals") @Cached EqualsNode equalsNode) {
|
||||||
var mapBuilder = ensoMap.getMapBuilder(false, hashCodeNode, equalsNode);
|
var mapBuilder = ensoMap.getMapBuilder(frame, false, hashCodeNode, equalsNode);
|
||||||
if (mapBuilder.remove(key, hashCodeNode, equalsNode)) {
|
if (mapBuilder.remove(frame, key, hashCodeNode, equalsNode)) {
|
||||||
return mapBuilder.build();
|
return mapBuilder.build();
|
||||||
} else {
|
} else {
|
||||||
throw DataflowError.withoutTrace("No such key", null);
|
throw DataflowError.withoutTrace("No such key", null);
|
||||||
@ -47,6 +49,7 @@ public abstract class HashMapRemoveNode extends Node {
|
|||||||
|
|
||||||
@Specialization(guards = "interop.hasHashEntries(map)")
|
@Specialization(guards = "interop.hasHashEntries(map)")
|
||||||
EnsoHashMap removeFromInteropMap(
|
EnsoHashMap removeFromInteropMap(
|
||||||
|
VirtualFrame frame,
|
||||||
Object map,
|
Object map,
|
||||||
Object keyToRemove,
|
Object keyToRemove,
|
||||||
@CachedLibrary(limit = "5") InteropLibrary interop,
|
@CachedLibrary(limit = "5") InteropLibrary interop,
|
||||||
@ -62,7 +65,7 @@ public abstract class HashMapRemoveNode extends Node {
|
|||||||
while (interop.hasIteratorNextElement(entriesIterator)) {
|
while (interop.hasIteratorNextElement(entriesIterator)) {
|
||||||
Object keyValueArr = interop.getIteratorNextElement(entriesIterator);
|
Object keyValueArr = interop.getIteratorNextElement(entriesIterator);
|
||||||
Object key = interop.readArrayElement(keyValueArr, 0);
|
Object key = interop.readArrayElement(keyValueArr, 0);
|
||||||
if ((boolean) equalsNode.execute(keyToRemove, key)) {
|
if (equalsNode.execute(frame, keyToRemove, key)) {
|
||||||
if (keyToRemoveFound) {
|
if (keyToRemoveFound) {
|
||||||
CompilerDirectives.transferToInterpreter();
|
CompilerDirectives.transferToInterpreter();
|
||||||
var ctx = EnsoContext.get(this);
|
var ctx = EnsoContext.get(this);
|
||||||
@ -72,8 +75,9 @@ public abstract class HashMapRemoveNode extends Node {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Object value = interop.readArrayElement(keyValueArr, 1);
|
Object value = interop.readArrayElement(keyValueArr, 1);
|
||||||
mapBuilder = mapBuilder.asModifiable(mapBuilder.generation(), hashCodeNode, equalsNode);
|
mapBuilder =
|
||||||
mapBuilder.put(key, value, hashCodeNode, equalsNode);
|
mapBuilder.asModifiable(frame, mapBuilder.generation(), hashCodeNode, equalsNode);
|
||||||
|
mapBuilder.put(frame, key, value, hashCodeNode, equalsNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (UnsupportedMessageException | StopIterationException | InvalidArrayIndexException e) {
|
} catch (UnsupportedMessageException | StopIterationException | InvalidArrayIndexException e) {
|
||||||
|
@ -231,7 +231,7 @@ public final class Warning implements EnsoObject {
|
|||||||
public Warning reassign(Node location) {
|
public Warning reassign(Node location) {
|
||||||
RootNode root = location.getRootNode();
|
RootNode root = location.getRootNode();
|
||||||
SourceSection section = location.getEncapsulatingSourceSection();
|
SourceSection section = location.getEncapsulatingSourceSection();
|
||||||
Reassignment reassignment = new Reassignment(root.getName(), section);
|
Reassignment reassignment = new Reassignment(root == null ? "" : root.getName(), section);
|
||||||
return new Warning(value, origin, sequenceId, reassignments.prepend(reassignment));
|
return new Warning(value, origin, sequenceId, reassignments.prepend(reassignment));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,19 +27,17 @@ type Complex
|
|||||||
< self (that:Complex) = Complex_Comparator.compare self that == Ordering.Less
|
< self (that:Complex) = Complex_Comparator.compare self that == Ordering.Less
|
||||||
> self (that:Complex) = Complex_Comparator.compare self that == Ordering.Greater
|
> self (that:Complex) = Complex_Comparator.compare self that == Ordering.Greater
|
||||||
|
|
||||||
== self (that:Complex) = Complex_Comparator.compare self that == Ordering.Equal
|
|
||||||
|
|
||||||
pending_equality -> Text = "== with conversions isn't yet supported"
|
|
||||||
|
|
||||||
Complex.from (that:Number) = Complex.new that
|
Complex.from (that:Number) = Complex.new that
|
||||||
|
|
||||||
type Complex_Comparator
|
type Complex_Comparator
|
||||||
compare x:Complex y:Complex = if x.re==y.re && x.im==y.im then Ordering.Equal else
|
compare x:Complex y:Complex = if x.re==y.re && x.im==y.im then Ordering.Equal else
|
||||||
if x.im==0 && y.im==0 then Ordering.compare x.re y.re else
|
if x.im==0 && y.im==0 then Ordering.compare x.re y.re else
|
||||||
Nothing
|
Nothing
|
||||||
hash x:Complex = 7*x.re + 11*x.im
|
hash x:Complex = if x.im == 0 then Default_Comparator.hash x.re else
|
||||||
|
7*x.re + 11*x.im
|
||||||
|
|
||||||
Comparable.from (_:Complex) = Complex_Comparator
|
Comparable.from (_:Complex) = Complex_Comparator
|
||||||
|
Comparable.from (_:Number) = Complex_Comparator
|
||||||
|
|
||||||
|
|
||||||
add_specs suite_builder =
|
add_specs suite_builder =
|
||||||
@ -591,21 +589,25 @@ add_specs suite_builder =
|
|||||||
v2 = (Complex.new 6 6)
|
v2 = (Complex.new 6 6)
|
||||||
Ordering.compare v1 v2 . catch Incomparable_Values (_->42) . should_equal 42
|
Ordering.compare v1 v2 . catch Incomparable_Values (_->42) . should_equal 42
|
||||||
|
|
||||||
group_builder.specify "Equality of complex and number" pending=Complex.pending_equality <|
|
group_builder.specify "Equality of complex and number" <|
|
||||||
v1 = (Complex.new 3)
|
v1 = (Complex.new 3)
|
||||||
v2 = 3
|
v2 = 3
|
||||||
|
r1 = v1==v2
|
||||||
|
r2 = v2==v1
|
||||||
|
r1 . should_be_true
|
||||||
|
r2 . should_be_true
|
||||||
v1 . should_equal v2
|
v1 . should_equal v2
|
||||||
v2 . should_equal v1
|
v2 . should_equal v1
|
||||||
v1==v2 . should_be_true
|
|
||||||
v2==v1 . should_be_true
|
|
||||||
|
|
||||||
group_builder.specify "Equality of number and complex" pending=Complex.pending_equality <|
|
group_builder.specify "Equality of number and complex" <|
|
||||||
v1 = 3
|
v1 = 3
|
||||||
v2 = (Complex.new 3)
|
v2 = (Complex.new 3)
|
||||||
|
r1 = v1==v2
|
||||||
|
r2 = v2==v1
|
||||||
|
r1 . should_be_true
|
||||||
|
r2 . should_be_true
|
||||||
v1 . should_equal v2
|
v1 . should_equal v2
|
||||||
v2 . should_equal v1
|
v2 . should_equal v1
|
||||||
v1==v2 . should_be_true
|
|
||||||
v2==v1 . should_be_true
|
|
||||||
|
|
||||||
group_builder.specify "Greater or equal of complex and complex" <|
|
group_builder.specify "Greater or equal of complex and complex" <|
|
||||||
v1 = (Complex.new 3)
|
v1 = (Complex.new 3)
|
||||||
|
@ -235,6 +235,8 @@ add_specs suite_builder =
|
|||||||
# no conversions needed when calling `exchange` methods
|
# no conversions needed when calling `exchange` methods
|
||||||
nine . should_equal one
|
nine . should_equal one
|
||||||
|
|
||||||
|
suite_builder.group "MultiValue Conversions" group_builder->
|
||||||
|
|
||||||
group_builder.specify "Requesting Text & Foo" <|
|
group_builder.specify "Requesting Text & Foo" <|
|
||||||
check a (n : Text & Foo) = case a of
|
check a (n : Text & Foo) = case a of
|
||||||
0 -> n.foo
|
0 -> n.foo
|
||||||
|
Loading…
Reference in New Issue
Block a user