mirror of
https://github.com/enso-org/enso.git
synced 2024-12-22 16:11:45 +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 =
|
||||
matches = case that of
|
||||
_ : Number -> self.equals that epsilon
|
||||
_ -> False
|
||||
_ -> self==that
|
||||
case matches of
|
||||
True -> Spec_Result.Success
|
||||
False ->
|
||||
@ -519,7 +519,7 @@ Error.should_contain_the_same_elements_as self that frames_to_skip=0 =
|
||||
|
||||
It checks that all elements from `self` are also present in `that`. It does
|
||||
not require that all elements of `that` are contained in `self`. Arities of
|
||||
elements are not checked, so `self` may still contain more elements than
|
||||
elements are not checked, so `self` may still contain more elements than
|
||||
`that` by containing duplicates.
|
||||
|
||||
It will work on any collection which supports the methods
|
||||
|
@ -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
|
||||
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
|
||||
|
||||
Operator precedence in Enso is a collection of rules that reflect conventions
|
||||
|
@ -63,10 +63,13 @@ another.
|
||||
|
||||
## Multiple Dispatch
|
||||
|
||||
It is 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.
|
||||
Multiple dispatch is currently used for
|
||||
[binary operators](../syntax/functions.md#type-ascriptions-and-operator-resolution).
|
||||
|
||||
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
|
||||
specification of the specificity algorithm. It must account for:
|
||||
|
@ -75,6 +75,21 @@ public class EqualsBenchmarks {
|
||||
new StringBuilder(
|
||||
"""
|
||||
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
|
||||
C1 f1
|
||||
@ -87,7 +102,9 @@ public class EqualsBenchmarks {
|
||||
|
||||
eq_vec vec1 vec2 =
|
||||
(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
|
||||
""");
|
||||
@ -108,10 +125,37 @@ public class EqualsBenchmarks {
|
||||
primitiveVectorSize / 64);
|
||||
codeBuilder
|
||||
.append(
|
||||
generateVectorOfPrimitives(primitiveVectorSize, "vec1", 42, trueExpectedAt, random))
|
||||
generateVectorOfPrimitives(
|
||||
primitiveVectorSize, "vec1", 42, trueExpectedAt, random, "%d", "%f"))
|
||||
.append("\n")
|
||||
.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");
|
||||
}
|
||||
case "equalsStrings" -> {
|
||||
@ -141,7 +185,7 @@ public class EqualsBenchmarks {
|
||||
}
|
||||
|
||||
codeBuilder.append("""
|
||||
bench x = eq_vec vec1 vec2
|
||||
bench _ = eq_vec vec1 vec2
|
||||
""");
|
||||
|
||||
module = ctx.eval(SrcUtil.source(benchmarkName, codeBuilder.toString()));
|
||||
@ -171,6 +215,11 @@ public class EqualsBenchmarks {
|
||||
performBenchmark(blackHole);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void equalsWithConversion(Blackhole blackHole) {
|
||||
performBenchmark(blackHole);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void equalsStrings(Blackhole blackhole) {
|
||||
performBenchmark(blackhole);
|
||||
@ -207,7 +256,9 @@ public class EqualsBenchmarks {
|
||||
String vecName,
|
||||
Object identityElem,
|
||||
Collection<Integer> constantIdxs,
|
||||
Random random) {
|
||||
Random random,
|
||||
String intFormat,
|
||||
String doubleFormat) {
|
||||
var partSize = totalSize / 2;
|
||||
List<Object> primitiveValues = new ArrayList<>();
|
||||
random.ints(partSize).forEach(primitiveValues::add);
|
||||
@ -221,9 +272,9 @@ public class EqualsBenchmarks {
|
||||
sb.append(vecName).append(" = [");
|
||||
for (Object primitiveValue : primitiveValues) {
|
||||
if (primitiveValue instanceof Double dbl) {
|
||||
sb.append(String.format("%f", dbl)).append(",");
|
||||
sb.append(String.format(doubleFormat, dbl)).append(",");
|
||||
} else {
|
||||
sb.append(primitiveValue).append(",");
|
||||
sb.append(String.format(intFormat, primitiveValue)).append(",");
|
||||
}
|
||||
}
|
||||
// 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.fail;
|
||||
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import java.math.BigInteger;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
@ -16,7 +17,6 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
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.EqualsNodeGen;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
|
||||
import org.enso.interpreter.runtime.number.EnsoBigInteger;
|
||||
import org.enso.polyglot.MethodNames;
|
||||
@ -44,8 +44,8 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
testRootNode = new TestRootNode();
|
||||
equalsNode = EqualsNode.build();
|
||||
testRootNode = new TestRootNode(EqualsTest::equalityCheck);
|
||||
equalsNode = EqualsNode.create();
|
||||
hostValueToEnsoNode = HostValueToEnsoNode.build();
|
||||
testRootNode.insertChildren(equalsNode, hostValueToEnsoNode);
|
||||
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
|
||||
public void equalsOperatorShouldBeSymmetric(Object firstValue, Object secondValue) {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
boolean firstResult = equalsNode.execute(firstValue, secondValue);
|
||||
boolean secondResult = equalsNode.execute(secondValue, firstValue);
|
||||
boolean firstResult = equalityCheck(firstValue, secondValue);
|
||||
boolean secondResult = equalityCheck(secondValue, firstValue);
|
||||
assertEquals("equals should be symmetric", firstResult, secondResult);
|
||||
return null;
|
||||
});
|
||||
@ -105,8 +114,8 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
Object firstResult = equalsNode.execute(value, value);
|
||||
Object secondResult = equalsNode.execute(value, value);
|
||||
Object firstResult = equalityCheck(value, value);
|
||||
Object secondResult = equalityCheck(value, value);
|
||||
assertEquals("equals should be consistent", firstResult, secondResult);
|
||||
return null;
|
||||
});
|
||||
@ -117,8 +126,8 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
Object uncachedRes = EqualsNodeGen.getUncached().execute(firstVal, secondVal);
|
||||
Object cachedRes = equalsNode.execute(firstVal, secondVal);
|
||||
Object uncachedRes = EqualsNode.getUncached().execute(null, firstVal, secondVal);
|
||||
Object cachedRes = equalityCheck(firstVal, secondVal);
|
||||
assertEquals(
|
||||
"Result from uncached EqualsNode should be the same as result from its cached"
|
||||
+ " variant",
|
||||
@ -140,7 +149,7 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(equalsNode.execute(ensoDate, javaDate));
|
||||
assertTrue(equalityCheck(ensoDate, javaDate));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@ -158,7 +167,7 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(equalsNode.execute(ensoTime, javaDate));
|
||||
assertTrue(equalityCheck(ensoTime, javaDate));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@ -181,7 +190,7 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(equalsNode.execute(ensoDateTime, javaDateTime));
|
||||
assertTrue(equalityCheck(ensoDateTime, javaDateTime));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@ -194,7 +203,7 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(javaNumber + " == " + ensoNumber, equalsNode.execute(javaNumber, ensoNumber));
|
||||
assertTrue(javaNumber + " == " + ensoNumber, equalityCheck(javaNumber, ensoNumber));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@ -207,7 +216,7 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(ensoNumber + " == " + javaNumber, equalsNode.execute(ensoNumber, javaNumber));
|
||||
assertTrue(ensoNumber + " == " + javaNumber, equalityCheck(ensoNumber, javaNumber));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@ -220,7 +229,7 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(javaNumber + " == " + hostNumber, equalsNode.execute(javaNumber, hostNumber));
|
||||
assertTrue(javaNumber + " == " + hostNumber, equalityCheck(javaNumber, hostNumber));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@ -233,7 +242,7 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(hostNumber + " == " + javaNumber, equalsNode.execute(hostNumber, javaNumber));
|
||||
assertTrue(hostNumber + " == " + javaNumber, equalityCheck(hostNumber, javaNumber));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@ -246,7 +255,7 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(equalsNode.execute(ensoVector, javaVector));
|
||||
assertTrue(equalityCheck(ensoVector, javaVector));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@ -258,9 +267,9 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(equalsNode.execute(ensoNumber, foreignNumber.asDirect()));
|
||||
assertTrue(equalsNode.execute(ensoNumber, foreignNumber));
|
||||
assertTrue(equalsNode.execute(foreignNumber, ensoNumber));
|
||||
assertTrue(equalityCheck(ensoNumber, foreignNumber.asDirect()));
|
||||
assertTrue(equalityCheck(ensoNumber, foreignNumber));
|
||||
assertTrue(equalityCheck(foreignNumber, ensoNumber));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@ -272,9 +281,9 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(equalsNode.execute(ensoNumber, foreignNumber.asDirect()));
|
||||
assertTrue(equalsNode.execute(ensoNumber, foreignNumber));
|
||||
assertTrue(equalsNode.execute(foreignNumber, ensoNumber));
|
||||
assertTrue(equalityCheck(ensoNumber, foreignNumber.asDirect()));
|
||||
assertTrue(equalityCheck(ensoNumber, foreignNumber));
|
||||
assertTrue(equalityCheck(foreignNumber, ensoNumber));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@ -287,8 +296,8 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(equalsNode.execute(ensoNumber, foreignNumber));
|
||||
assertTrue(equalsNode.execute(foreignNumber, ensoNumber));
|
||||
assertTrue(equalityCheck(ensoNumber, foreignNumber));
|
||||
assertTrue(equalityCheck(foreignNumber, ensoNumber));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@ -301,9 +310,9 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(equalsNode.execute(ensoBoolean, foreignBoolean.asDirect()));
|
||||
assertTrue(equalsNode.execute(ensoBoolean, foreignBoolean));
|
||||
assertTrue(equalsNode.execute(foreignBoolean, ensoBoolean));
|
||||
assertTrue(equalityCheck(ensoBoolean, foreignBoolean.asDirect()));
|
||||
assertTrue(equalityCheck(ensoBoolean, foreignBoolean));
|
||||
assertTrue(equalityCheck(foreignBoolean, ensoBoolean));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@ -315,9 +324,9 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(equalsNode.execute(ensoText, foreignString.asDirect()));
|
||||
assertTrue(equalsNode.execute(ensoText, foreignString));
|
||||
assertTrue(equalsNode.execute(foreignString, ensoText));
|
||||
assertTrue(equalityCheck(ensoText, foreignString.asDirect()));
|
||||
assertTrue(equalityCheck(ensoText, foreignString));
|
||||
assertTrue(equalityCheck(foreignString, ensoText));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@ -336,8 +345,8 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(equalsNode.execute(142L, hundred42));
|
||||
assertTrue(equalsNode.execute(hundred42, 142L));
|
||||
assertTrue(equalityCheck(142L, hundred42));
|
||||
assertTrue(equalityCheck(hundred42, 142L));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@ -375,15 +384,12 @@ public class EqualsTest extends TestBase {
|
||||
executeInContext(
|
||||
context,
|
||||
() -> {
|
||||
assertTrue(
|
||||
"Conversions from same module are the same", equalsNode.execute(conv1, conv1_2));
|
||||
assertTrue(
|
||||
"Conversions from same module are the same", equalsNode.execute(conv2, conv2_2));
|
||||
assertTrue("Conversions from same module are the same", equalityCheck(conv1, conv1_2));
|
||||
assertTrue("Conversions from same module are the same", equalityCheck(conv2, conv2_2));
|
||||
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(
|
||||
"Conversions from other modueles aren't the same",
|
||||
equalsNode.execute(conv2_2, conv1_2));
|
||||
"Conversions from other modueles aren't the same", equalityCheck(conv2_2, conv1_2));
|
||||
return null;
|
||||
});
|
||||
|
||||
|
@ -38,7 +38,7 @@ public class HashCodeTest extends TestBase {
|
||||
context,
|
||||
() -> {
|
||||
hashCodeNode = HashCodeNode.build();
|
||||
equalsNode = EqualsNode.build();
|
||||
equalsNode = EqualsNode.create();
|
||||
hostValueToEnsoNode = HostValueToEnsoNode.build();
|
||||
testRootNode = new TestRootNode();
|
||||
testRootNode.insertChildren(hashCodeNode, equalsNode, hostValueToEnsoNode);
|
||||
@ -94,7 +94,7 @@ public class HashCodeTest extends TestBase {
|
||||
() -> {
|
||||
long firstHash = hashCodeNode.execute(firstValue);
|
||||
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 (isTrue(valuesAreEqual)) {
|
||||
assertEquals(
|
||||
|
@ -13,6 +13,7 @@ import java.io.OutputStream;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import org.enso.interpreter.EnsoLanguage;
|
||||
import org.enso.polyglot.MethodNames.Module;
|
||||
@ -45,6 +46,7 @@ public abstract class TestBase {
|
||||
.allowIO(IOAccess.ALL)
|
||||
.allowAllAccess(true)
|
||||
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
|
||||
.option(RuntimeOptions.DISABLE_IR_CACHES, "true")
|
||||
.logHandler(System.err)
|
||||
.option(RuntimeOptions.STRICT_ERRORS, "true")
|
||||
.option(
|
||||
@ -152,8 +154,15 @@ public abstract class TestBase {
|
||||
* #insertChildren(Node...)}.
|
||||
*/
|
||||
static class TestRootNode extends RootNode {
|
||||
private final Function<VirtualFrame, Object> callback;
|
||||
|
||||
TestRootNode() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
TestRootNode(Function<VirtualFrame, Object> callback) {
|
||||
super(EnsoLanguage.get(null));
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
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. */
|
||||
@Override
|
||||
public Object execute(VirtualFrame frame) {
|
||||
throw new AssertionError("should not reach here");
|
||||
if (callback == null) {
|
||||
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.GenerateUncached;
|
||||
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.nodes.ExplodeLoop;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
@ -32,7 +34,7 @@ public abstract class EqualsAtomNode extends Node {
|
||||
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) {
|
||||
EqualsNode[] nodes = new EqualsNode[size];
|
||||
@ -48,6 +50,7 @@ public abstract class EqualsAtomNode extends Node {
|
||||
limit = "10")
|
||||
@ExplodeLoop
|
||||
boolean equalsAtomsWithDefaultComparator(
|
||||
VirtualFrame frame,
|
||||
Atom self,
|
||||
Atom other,
|
||||
@Cached("self.getConstructor()") AtomConstructor selfCtorCached,
|
||||
@ -65,7 +68,7 @@ public abstract class EqualsAtomNode extends Node {
|
||||
for (int i = 0; i < fieldsLenCached; i++) {
|
||||
var selfValue = structsLib.getField(self, i);
|
||||
var otherValue = structsLib.getField(other, i);
|
||||
var fieldsAreEqual = fieldEqualsNodes[i].execute(selfValue, otherValue);
|
||||
var fieldsAreEqual = fieldEqualsNodes[i].execute(frame, selfValue, otherValue);
|
||||
if (!fieldsAreEqual) {
|
||||
return false;
|
||||
}
|
||||
@ -116,13 +119,18 @@ public abstract class EqualsAtomNode extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
@Specialization(
|
||||
replaces = {"equalsAtomsWithDefaultComparator", "equalsAtomsWithCustomComparator"})
|
||||
boolean equalsAtomsUncached(Atom self, Atom other) {
|
||||
boolean equalsAtomsUncached(VirtualFrame frame, Atom self, Atom other) {
|
||||
if (self.getConstructor() != other.getConstructor()) {
|
||||
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);
|
||||
if (customComparator != null) {
|
||||
Function compareFunc = findCompareMethod(customComparator);
|
||||
@ -139,7 +147,7 @@ public abstract class EqualsAtomNode extends Node {
|
||||
for (int i = 0; i < self.getConstructor().getArity(); i++) {
|
||||
var selfField = StructsLibrary.getUncached().getField(self, 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) {
|
||||
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.GenerateUncached;
|
||||
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.InteropLibrary;
|
||||
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 java.time.LocalDateTime;
|
||||
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.ordering.CustomComparatorNode;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
import org.enso.interpreter.runtime.Module;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
|
||||
@ -39,24 +38,26 @@ public abstract class EqualsComplexNode extends Node {
|
||||
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 */
|
||||
@Specialization
|
||||
boolean equalsUnresolvedSymbols(
|
||||
VirtualFrame frame,
|
||||
UnresolvedSymbol self,
|
||||
UnresolvedSymbol otherSymbol,
|
||||
@Shared("equalsNode") @Cached EqualsNode equalsNode) {
|
||||
return self.getName().equals(otherSymbol.getName())
|
||||
&& equalsNode.execute(self.getScope(), otherSymbol.getScope());
|
||||
&& equalsNode.execute(frame, self.getScope(), otherSymbol.getScope());
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsUnresolvedConversion(
|
||||
VirtualFrame frame,
|
||||
UnresolvedConversion selfConversion,
|
||||
UnresolvedConversion otherConversion,
|
||||
@Shared("equalsNode") @Cached EqualsNode equalsNode) {
|
||||
return equalsNode.execute(selfConversion.getScope(), otherConversion.getScope());
|
||||
return equalsNode.execute(frame, selfConversion.getScope(), otherConversion.getScope());
|
||||
}
|
||||
|
||||
@Specialization
|
||||
@ -71,8 +72,11 @@ public abstract class EqualsComplexNode extends Node {
|
||||
|
||||
@Specialization
|
||||
boolean equalsFiles(
|
||||
EnsoFile selfFile, EnsoFile otherFile, @Shared("equalsNode") @Cached EqualsNode equalsNode) {
|
||||
return equalsNode.execute(selfFile.getPath(), otherFile.getPath());
|
||||
VirtualFrame frame,
|
||||
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)"})
|
||||
boolean equalsTypes(
|
||||
VirtualFrame frame,
|
||||
Type selfType,
|
||||
Type otherType,
|
||||
@Shared("equalsNode") @Cached EqualsNode equalsNode,
|
||||
@Shared("typesLib") @CachedLibrary(limit = "10") TypesLibrary typesLib) {
|
||||
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")
|
||||
boolean equalsWithWarnings(
|
||||
VirtualFrame frame,
|
||||
Object selfWithWarnings,
|
||||
Object otherWithWarnings,
|
||||
@CachedLibrary("selfWithWarnings") WarningsLibrary selfWarnLib,
|
||||
@ -113,7 +119,7 @@ public abstract class EqualsComplexNode extends Node {
|
||||
otherWarnLib.hasWarnings(otherWithWarnings)
|
||||
? otherWarnLib.removeWarnings(otherWithWarnings)
|
||||
: otherWithWarnings;
|
||||
return equalsNode.execute(self, other);
|
||||
return equalsNode.execute(frame, self, other);
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw EnsoContext.get(this).raiseAssertionPanic(this, null, e);
|
||||
}
|
||||
@ -278,12 +284,12 @@ public abstract class EqualsComplexNode extends Node {
|
||||
},
|
||||
limit = "3")
|
||||
boolean equalsArrays(
|
||||
VirtualFrame frame,
|
||||
Object selfArray,
|
||||
Object otherArray,
|
||||
@CachedLibrary("selfArray") InteropLibrary selfInterop,
|
||||
@CachedLibrary("otherArray") InteropLibrary otherInterop,
|
||||
@Shared("equalsNode") @Cached EqualsNode equalsNode,
|
||||
@Cached CustomComparatorNode hasCustomComparatorNode,
|
||||
@Shared("hostValueToEnsoNode") @Cached HostValueToEnsoNode valueToEnsoNode) {
|
||||
try {
|
||||
long selfSize = selfInterop.getArraySize(selfArray);
|
||||
@ -293,7 +299,7 @@ public abstract class EqualsComplexNode extends Node {
|
||||
for (long i = 0; i < selfSize; i++) {
|
||||
Object selfElem = valueToEnsoNode.execute(selfInterop.readArrayElement(selfArray, i));
|
||||
Object otherElem = valueToEnsoNode.execute(otherInterop.readArrayElement(otherArray, i));
|
||||
boolean elemsAreEqual = equalsNode.execute(selfElem, otherElem);
|
||||
boolean elemsAreEqual = equalsNode.execute(frame, selfElem, otherElem);
|
||||
if (!elemsAreEqual) {
|
||||
return false;
|
||||
}
|
||||
@ -313,6 +319,7 @@ public abstract class EqualsComplexNode extends Node {
|
||||
},
|
||||
limit = "3")
|
||||
boolean equalsHashMaps(
|
||||
VirtualFrame frame,
|
||||
Object selfHashMap,
|
||||
Object otherHashMap,
|
||||
@CachedLibrary("selfHashMap") InteropLibrary selfInterop,
|
||||
@ -337,7 +344,7 @@ public abstract class EqualsComplexNode extends Node {
|
||||
&& otherInterop.isHashEntryReadable(otherHashMap, key)) {
|
||||
Object otherValue =
|
||||
valueToEnsoNode.execute(otherInterop.readHashValue(otherHashMap, key));
|
||||
if (!equalsNode.execute(selfValue, otherValue)) {
|
||||
if (!equalsNode.execute(frame, selfValue, otherValue)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@ -391,13 +398,14 @@ public abstract class EqualsComplexNode extends Node {
|
||||
// It has well-defined equality based on the qualified name.
|
||||
@Specialization(guards = {"isJavaFunction(selfHostFunc)", "isJavaFunction(otherHostFunc)"})
|
||||
boolean equalsHostFunctions(
|
||||
VirtualFrame frame,
|
||||
Object selfHostFunc,
|
||||
Object otherHostFunc,
|
||||
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop,
|
||||
@Shared("equalsNode") @Cached EqualsNode equalsNode) {
|
||||
Object selfFuncStrRepr = interop.toDisplayString(selfHostFunc);
|
||||
Object otherFuncStrRepr = interop.toDisplayString(otherHostFunc);
|
||||
return equalsNode.execute(selfFuncStrRepr, otherFuncStrRepr);
|
||||
return equalsNode.execute(frame, selfFuncStrRepr, otherFuncStrRepr);
|
||||
}
|
||||
|
||||
@Specialization(guards = "fallbackGuard(left, right, interop, warningsLib)")
|
||||
@ -419,7 +427,8 @@ public abstract class EqualsComplexNode extends Node {
|
||||
// all the other guards on purpose.
|
||||
boolean fallbackGuard(
|
||||
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;
|
||||
}
|
||||
if (isJavaObject(left) && isJavaObject(right)) {
|
||||
|
@ -1,27 +1,29 @@
|
||||
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.Shared;
|
||||
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.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.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.interop.ArityException;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import java.math.BigInteger;
|
||||
import org.enso.interpreter.dsl.AcceptsError;
|
||||
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.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;
|
||||
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
|
||||
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.data.Type;
|
||||
import org.enso.interpreter.runtime.error.PanicException;
|
||||
import org.enso.interpreter.runtime.scope.ModuleScope;
|
||||
import org.enso.interpreter.runtime.state.State;
|
||||
|
||||
@BuiltinMethod(
|
||||
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`
|
||||
implies `Any.==` for all object with the exception of `Number.nan`.
|
||||
""")
|
||||
@GenerateUncached
|
||||
public abstract class EqualsNode extends Node {
|
||||
public final class EqualsNode extends Node {
|
||||
@Child private EqualsSimpleNode node;
|
||||
@Child private TypeOfNode types;
|
||||
@Child private WithConversionNode convert;
|
||||
|
||||
public static EqualsNode build() {
|
||||
return EqualsNodeGen.create();
|
||||
}
|
||||
private static final EqualsNode UNCACHED =
|
||||
new EqualsNode(EqualsSimpleNodeGen.getUncached(), TypeOfNode.getUncached(), true);
|
||||
|
||||
public abstract boolean execute(@AcceptsError Object self, @AcceptsError 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;
|
||||
private EqualsNode(EqualsSimpleNode node, TypeOfNode types, boolean uncached) {
|
||||
this.node = node;
|
||||
this.types = types;
|
||||
if (uncached) {
|
||||
convert = EqualsNodeFactory.WithConversionNodeGen.getUncached();
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsLongLong(long self, long other) {
|
||||
return self == other;
|
||||
@NeverDefault
|
||||
static EqualsNode build() {
|
||||
return create();
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsLongBool(long self, boolean other) {
|
||||
return false;
|
||||
@NeverDefault
|
||||
public static EqualsNode create() {
|
||||
return new EqualsNode(EqualsSimpleNode.build(), TypeOfNode.build(), false);
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsLongDouble(long self, double other) {
|
||||
return (double) self == other;
|
||||
@NeverDefault
|
||||
public static EqualsNode getUncached() {
|
||||
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);
|
||||
/**
|
||||
* Compares two objects for equality. If the {@link EqualsSimpleNode simple check} fails, it tries
|
||||
* to convert first argument to the second one and compare again.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
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);
|
||||
}
|
||||
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;
|
||||
return areEqual;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
* A node that checks the type of {@code that} argument, performs conversion of {@code self} to
|
||||
* {@code that} type, and executes equality check again.
|
||||
*/
|
||||
@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) {
|
||||
@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(
|
||||
limit = "10",
|
||||
guards = {
|
||||
"selfType != null",
|
||||
"thatType != null",
|
||||
"selfType == findType(typeOfNode, self)",
|
||||
"thatType == findType(typeOfNode, that)"
|
||||
})
|
||||
final boolean doConversionCached(
|
||||
VirtualFrame frame,
|
||||
Object self,
|
||||
Object that,
|
||||
@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;
|
||||
}
|
||||
if (convert) {
|
||||
return doDispatch(frame, that, self, thatType, convertNode, equalityNode);
|
||||
} else {
|
||||
return doDispatch(frame, self, that, selfType, convertNode, equalityNode);
|
||||
}
|
||||
}
|
||||
|
||||
@Specialization(replaces = "doConversionCached")
|
||||
final boolean doConversionUncached(
|
||||
VirtualFrame frame,
|
||||
Object self,
|
||||
Object that,
|
||||
@Shared("typeOf") @Cached TypeOfNode typeOfNode,
|
||||
@Shared("convert") @Cached InteropConversionCallNode convertNode,
|
||||
@Shared("invoke") @Cached(allowUncached = true) EqualsSimpleNode equalityNode) {
|
||||
var selfType = findType(typeOfNode, self);
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
private boolean doDispatch(
|
||||
VirtualFrame frame,
|
||||
Object self,
|
||||
Object that,
|
||||
Type selfType,
|
||||
InteropConversionCallNode convertNode,
|
||||
EqualsSimpleNode equalityNode)
|
||||
throws PanicException {
|
||||
var convert = UnresolvedConversion.build(selfType.getDefinitionScope());
|
||||
|
||||
/** Equals for Atoms and AtomConstructors */
|
||||
@Specialization
|
||||
boolean equalsAtomConstructors(AtomConstructor self, AtomConstructor other) {
|
||||
return self == other;
|
||||
}
|
||||
|
||||
@Specialization
|
||||
boolean equalsAtoms(
|
||||
Atom self,
|
||||
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 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(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 {
|
||||
var ctx = EnsoContext.get(this);
|
||||
var state = State.create(ctx);
|
||||
try {
|
||||
return iop.asBigInteger(v);
|
||||
} catch (UnsupportedMessageException ex) {
|
||||
throw EnsoContext.get(this).raiseAssertionPanic(this, "Expecting BigInteger", ex);
|
||||
var thatAsSelf = convertNode.execute(convert, state, new Object[] {selfType, that});
|
||||
var result = equalityNode.execute(frame, self, thatAsSelf);
|
||||
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();
|
||||
}
|
||||
|
||||
static HashCodeNode getUncached() {
|
||||
return HashCodeNodeGen.getUncached();
|
||||
}
|
||||
|
||||
public abstract long execute(@AcceptsError Object object);
|
||||
|
||||
/** Specializations for primitive values * */
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.enso.interpreter.node.expression.builtin.ordering;
|
||||
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.dsl.AcceptsError;
|
||||
import org.enso.interpreter.dsl.BuiltinMethod;
|
||||
@ -11,6 +12,7 @@ public final class SortArrayNode extends Node {
|
||||
@Child private SortVectorNode sortVectorNode = SortVectorNode.build();
|
||||
|
||||
public Object execute(
|
||||
VirtualFrame frame,
|
||||
State state,
|
||||
@AcceptsError Object self,
|
||||
long ascending,
|
||||
@ -20,7 +22,15 @@ public final class SortArrayNode extends Node {
|
||||
Object onFunc,
|
||||
long problemBehavior) {
|
||||
return sortVectorNode.execute(
|
||||
state, self, ascending, comparators, compareFunctions, byFunc, onFunc, problemBehavior);
|
||||
frame,
|
||||
state,
|
||||
self,
|
||||
ascending,
|
||||
comparators,
|
||||
compareFunctions,
|
||||
byFunc,
|
||||
onFunc,
|
||||
problemBehavior);
|
||||
}
|
||||
|
||||
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.GenerateUncached;
|
||||
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.InvalidArrayIndexException;
|
||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
@ -77,6 +79,7 @@ public abstract class SortVectorNode extends Node {
|
||||
* @return A new, sorted vector.
|
||||
*/
|
||||
public abstract Object execute(
|
||||
VirtualFrame frame,
|
||||
State state,
|
||||
@AcceptsError Object self,
|
||||
long ascending,
|
||||
@ -101,6 +104,7 @@ public abstract class SortVectorNode extends Node {
|
||||
"interop.isNull(onFunc)"
|
||||
})
|
||||
Object sortPrimitives(
|
||||
VirtualFrame frame,
|
||||
State state,
|
||||
Object self,
|
||||
long ascending,
|
||||
@ -131,7 +135,14 @@ public abstract class SortVectorNode extends Node {
|
||||
}
|
||||
var javaComparator =
|
||||
createDefaultComparator(
|
||||
lessThanNode, equalsNode, typeOfNode, toTextNode, ascending, problemBehavior, interop);
|
||||
frame.materialize(),
|
||||
lessThanNode,
|
||||
equalsNode,
|
||||
typeOfNode,
|
||||
toTextNode,
|
||||
ascending,
|
||||
problemBehavior,
|
||||
interop);
|
||||
try {
|
||||
return sortPrimitiveVector(elems, javaComparator);
|
||||
} catch (CompareException e) {
|
||||
@ -142,6 +153,7 @@ public abstract class SortVectorNode extends Node {
|
||||
|
||||
@TruffleBoundary
|
||||
private DefaultSortComparator createDefaultComparator(
|
||||
MaterializedFrame frame,
|
||||
LessThanNode lessThanNode,
|
||||
EqualsNode equalsNode,
|
||||
TypeOfNode typeOfNode,
|
||||
@ -150,6 +162,7 @@ public abstract class SortVectorNode extends Node {
|
||||
long problemBehaviorNum,
|
||||
InteropLibrary interop) {
|
||||
return new DefaultSortComparator(
|
||||
frame,
|
||||
lessThanNode,
|
||||
equalsNode,
|
||||
typeOfNode,
|
||||
@ -165,6 +178,7 @@ public abstract class SortVectorNode extends Node {
|
||||
"interop.hasArrayElements(self)",
|
||||
})
|
||||
Object sortGeneric(
|
||||
MaterializedFrame frame,
|
||||
State state,
|
||||
Object self,
|
||||
long ascending,
|
||||
@ -206,6 +220,7 @@ public abstract class SortVectorNode extends Node {
|
||||
if (interop.isNull(byFunc) && interop.isNull(onFunc) && isPrimitiveGroup(group)) {
|
||||
javaComparator =
|
||||
new DefaultSortComparator(
|
||||
frame,
|
||||
lessThanNode,
|
||||
equalsNode,
|
||||
typeOfNode,
|
||||
@ -557,12 +572,14 @@ public abstract class SortVectorNode extends Node {
|
||||
*/
|
||||
final class DefaultSortComparator extends SortComparator {
|
||||
|
||||
private final MaterializedFrame frame;
|
||||
private final LessThanNode lessThanNode;
|
||||
private final EqualsNode equalsNode;
|
||||
private final TypeOfNode typeOfNode;
|
||||
private final boolean ascending;
|
||||
|
||||
private DefaultSortComparator(
|
||||
MaterializedFrame frame,
|
||||
LessThanNode lessThanNode,
|
||||
EqualsNode equalsNode,
|
||||
TypeOfNode typeOfNode,
|
||||
@ -571,6 +588,7 @@ public abstract class SortVectorNode extends Node {
|
||||
ProblemBehavior problemBehavior,
|
||||
InteropLibrary interop) {
|
||||
super(toTextNode, problemBehavior, interop);
|
||||
this.frame = frame;
|
||||
this.lessThanNode = lessThanNode;
|
||||
this.equalsNode = equalsNode;
|
||||
this.typeOfNode = typeOfNode;
|
||||
@ -583,7 +601,7 @@ public abstract class SortVectorNode extends Node {
|
||||
}
|
||||
|
||||
int compareValuesWithDefaultComparator(Object x, Object y) {
|
||||
if (equalsNode.execute(x, y)) {
|
||||
if (equalsNode.execute(frame, x, y)) {
|
||||
return 0;
|
||||
} else {
|
||||
// Check if x < y
|
||||
|
@ -369,7 +369,7 @@ public final class EnsoMultiValue implements EnsoObject {
|
||||
try {
|
||||
var members = iop.getMembers(values[i]);
|
||||
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);
|
||||
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.dsl.Cached;
|
||||
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.UnknownKeyException;
|
||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
@ -55,11 +56,11 @@ public final class EnsoHashMap implements EnsoObject {
|
||||
}
|
||||
|
||||
EnsoHashMapBuilder getMapBuilder(
|
||||
boolean readOnly, HashCodeNode hashCodeNode, EqualsNode equalsNode) {
|
||||
VirtualFrame frame, boolean readOnly, HashCodeNode hashCodeNode, EqualsNode equalsNode) {
|
||||
if (readOnly) {
|
||||
return mapBuilder;
|
||||
} 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,
|
||||
@Shared("hash") @Cached HashCodeNode hashCodeNode,
|
||||
@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;
|
||||
}
|
||||
|
||||
@ -122,7 +123,7 @@ public final class EnsoHashMap implements EnsoObject {
|
||||
@Shared("hash") @Cached HashCodeNode hashCodeNode,
|
||||
@Shared("equals") @Cached EqualsNode equalsNode)
|
||||
throws UnknownKeyException {
|
||||
StorageEntry entry = mapBuilder.get(key, generation, hashCodeNode, equalsNode);
|
||||
StorageEntry entry = mapBuilder.get(null, key, generation, hashCodeNode, equalsNode);
|
||||
if (entry != null) {
|
||||
return entry.value();
|
||||
} else {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.enso.interpreter.runtime.data.hash;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import java.util.Arrays;
|
||||
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
|
||||
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.
|
||||
*/
|
||||
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) {
|
||||
var newSize = Math.max(actualSize * 2, byHash.length);
|
||||
return rehash(newSize, atGeneration, hashCodeNode, equalsNode);
|
||||
return rehash(frame, newSize, atGeneration, hashCodeNode, equalsNode);
|
||||
} else {
|
||||
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,
|
||||
* 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 nextGeneration = ++generation;
|
||||
var replacingExistingKey = false;
|
||||
@ -114,7 +120,7 @@ final class EnsoHashMapBuilder {
|
||||
byHash[at] = new StorageEntry(key, value, nextGeneration);
|
||||
return;
|
||||
}
|
||||
if (compare(equalsNode, byHash[at].key(), key)) {
|
||||
if (compare(frame, equalsNode, byHash[at].key(), key)) {
|
||||
var invalidatedEntry = byHash[at].markRemoved(nextGeneration);
|
||||
if (invalidatedEntry != byHash[at]) {
|
||||
byHash[at] = invalidatedEntry;
|
||||
@ -133,14 +139,18 @@ final class EnsoHashMapBuilder {
|
||||
* given {@code generation}.
|
||||
*/
|
||||
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);
|
||||
for (var i = 0; i < byHash.length; i++) {
|
||||
if (byHash[at] == null) {
|
||||
return null;
|
||||
}
|
||||
if (byHash[at].isVisible(generation)) {
|
||||
if (compare(equalsNode, key, byHash[at].key())) {
|
||||
if (compare(frame, equalsNode, key, byHash[at].key())) {
|
||||
return byHash[at];
|
||||
}
|
||||
}
|
||||
@ -164,14 +174,15 @@ final class EnsoHashMapBuilder {
|
||||
*
|
||||
* @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 nextGeneration = ++generation;
|
||||
for (var i = 0; i < byHash.length; i++) {
|
||||
if (byHash[at] == null) {
|
||||
return false;
|
||||
}
|
||||
if (compare(equalsNode, key, byHash[at].key())) {
|
||||
if (compare(frame, equalsNode, key, byHash[at].key())) {
|
||||
var invalidatedEntry = byHash[at].markRemoved(nextGeneration);
|
||||
if (invalidatedEntry != byHash[at]) {
|
||||
byHash[at] = invalidatedEntry;
|
||||
@ -191,12 +202,16 @@ final class EnsoHashMapBuilder {
|
||||
* atGeneration}.
|
||||
*/
|
||||
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);
|
||||
for (var i = 0; i < byHash.length; i++) {
|
||||
var entry = byHash[i];
|
||||
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;
|
||||
@ -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()) {
|
||||
return true;
|
||||
} 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.Shared;
|
||||
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.InvalidArrayIndexException;
|
||||
import com.oracle.truffle.api.interop.StopIterationException;
|
||||
@ -30,17 +31,18 @@ public abstract class HashMapInsertNode extends Node {
|
||||
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
|
||||
EnsoHashMap doEnsoHashMap(
|
||||
VirtualFrame frame,
|
||||
EnsoHashMap hashMap,
|
||||
Object key,
|
||||
Object value,
|
||||
@Shared("hash") @Cached HashCodeNode hashCodeNode,
|
||||
@Shared("equals") @Cached EqualsNode equalsNode) {
|
||||
var mapBuilder = hashMap.getMapBuilder(false, hashCodeNode, equalsNode);
|
||||
mapBuilder.put(key, value, hashCodeNode, equalsNode);
|
||||
var mapBuilder = hashMap.getMapBuilder(frame, false, hashCodeNode, equalsNode);
|
||||
mapBuilder.put(frame, key, value, hashCodeNode, equalsNode);
|
||||
var newMap = mapBuilder.build();
|
||||
return newMap;
|
||||
}
|
||||
@ -51,6 +53,7 @@ public abstract class HashMapInsertNode extends Node {
|
||||
*/
|
||||
@Specialization(guards = "mapInterop.hasHashEntries(foreignMap)", limit = "3")
|
||||
EnsoHashMap doForeign(
|
||||
VirtualFrame frame,
|
||||
Object foreignMap,
|
||||
Object keyToInsert,
|
||||
Object valueToInsert,
|
||||
@ -65,8 +68,9 @@ public abstract class HashMapInsertNode extends Node {
|
||||
Object keyValueArr = iteratorInterop.getIteratorNextElement(entriesIterator);
|
||||
Object key = iteratorInterop.readArrayElement(keyValueArr, 0);
|
||||
Object value = iteratorInterop.readArrayElement(keyValueArr, 1);
|
||||
mapBuilder = mapBuilder.asModifiable(mapBuilder.generation(), hashCodeNode, equalsNode);
|
||||
mapBuilder.put(key, value, hashCodeNode, equalsNode);
|
||||
mapBuilder =
|
||||
mapBuilder.asModifiable(frame, mapBuilder.generation(), hashCodeNode, equalsNode);
|
||||
mapBuilder.put(frame, key, value, hashCodeNode, equalsNode);
|
||||
}
|
||||
} catch (UnsupportedMessageException | StopIterationException | InvalidArrayIndexException e) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
@ -76,8 +80,8 @@ public abstract class HashMapInsertNode extends Node {
|
||||
+ " has wrongly specified Interop API (hash entries iterator)";
|
||||
throw new PanicException(Text.create(msg), this);
|
||||
}
|
||||
mapBuilder = mapBuilder.asModifiable(mapBuilder.generation(), hashCodeNode, equalsNode);
|
||||
mapBuilder.put(keyToInsert, valueToInsert, hashCodeNode, equalsNode);
|
||||
mapBuilder = mapBuilder.asModifiable(frame, mapBuilder.generation(), hashCodeNode, equalsNode);
|
||||
mapBuilder.put(frame, keyToInsert, valueToInsert, hashCodeNode, equalsNode);
|
||||
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.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.InvalidArrayIndexException;
|
||||
import com.oracle.truffle.api.interop.StopIterationException;
|
||||
@ -29,16 +30,17 @@ public abstract class HashMapRemoveNode extends Node {
|
||||
return HashMapRemoveNodeGen.create();
|
||||
}
|
||||
|
||||
public abstract EnsoHashMap execute(Object self, Object key);
|
||||
public abstract EnsoHashMap execute(VirtualFrame frame, Object self, Object key);
|
||||
|
||||
@Specialization
|
||||
EnsoHashMap removeFromEnsoMap(
|
||||
VirtualFrame frame,
|
||||
EnsoHashMap ensoMap,
|
||||
Object key,
|
||||
@Shared("hash") @Cached HashCodeNode hashCodeNode,
|
||||
@Shared("equals") @Cached EqualsNode equalsNode) {
|
||||
var mapBuilder = ensoMap.getMapBuilder(false, hashCodeNode, equalsNode);
|
||||
if (mapBuilder.remove(key, hashCodeNode, equalsNode)) {
|
||||
var mapBuilder = ensoMap.getMapBuilder(frame, false, hashCodeNode, equalsNode);
|
||||
if (mapBuilder.remove(frame, key, hashCodeNode, equalsNode)) {
|
||||
return mapBuilder.build();
|
||||
} else {
|
||||
throw DataflowError.withoutTrace("No such key", null);
|
||||
@ -47,6 +49,7 @@ public abstract class HashMapRemoveNode extends Node {
|
||||
|
||||
@Specialization(guards = "interop.hasHashEntries(map)")
|
||||
EnsoHashMap removeFromInteropMap(
|
||||
VirtualFrame frame,
|
||||
Object map,
|
||||
Object keyToRemove,
|
||||
@CachedLibrary(limit = "5") InteropLibrary interop,
|
||||
@ -62,7 +65,7 @@ public abstract class HashMapRemoveNode extends Node {
|
||||
while (interop.hasIteratorNextElement(entriesIterator)) {
|
||||
Object keyValueArr = interop.getIteratorNextElement(entriesIterator);
|
||||
Object key = interop.readArrayElement(keyValueArr, 0);
|
||||
if ((boolean) equalsNode.execute(keyToRemove, key)) {
|
||||
if (equalsNode.execute(frame, keyToRemove, key)) {
|
||||
if (keyToRemoveFound) {
|
||||
CompilerDirectives.transferToInterpreter();
|
||||
var ctx = EnsoContext.get(this);
|
||||
@ -72,8 +75,9 @@ public abstract class HashMapRemoveNode extends Node {
|
||||
}
|
||||
} else {
|
||||
Object value = interop.readArrayElement(keyValueArr, 1);
|
||||
mapBuilder = mapBuilder.asModifiable(mapBuilder.generation(), hashCodeNode, equalsNode);
|
||||
mapBuilder.put(key, value, hashCodeNode, equalsNode);
|
||||
mapBuilder =
|
||||
mapBuilder.asModifiable(frame, mapBuilder.generation(), hashCodeNode, equalsNode);
|
||||
mapBuilder.put(frame, key, value, hashCodeNode, equalsNode);
|
||||
}
|
||||
}
|
||||
} catch (UnsupportedMessageException | StopIterationException | InvalidArrayIndexException e) {
|
||||
|
@ -231,7 +231,7 @@ public final class Warning implements EnsoObject {
|
||||
public Warning reassign(Node location) {
|
||||
RootNode root = location.getRootNode();
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -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.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
|
||||
|
||||
type Complex_Comparator
|
||||
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
|
||||
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 (_:Number) = Complex_Comparator
|
||||
|
||||
|
||||
add_specs suite_builder =
|
||||
@ -591,21 +589,25 @@ add_specs suite_builder =
|
||||
v2 = (Complex.new 6 6)
|
||||
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)
|
||||
v2 = 3
|
||||
r1 = v1==v2
|
||||
r2 = v2==v1
|
||||
r1 . should_be_true
|
||||
r2 . should_be_true
|
||||
v1 . should_equal v2
|
||||
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
|
||||
v2 = (Complex.new 3)
|
||||
r1 = v1==v2
|
||||
r2 = v2==v1
|
||||
r1 . should_be_true
|
||||
r2 . should_be_true
|
||||
v1 . should_equal v2
|
||||
v2 . should_equal v1
|
||||
v1==v2 . should_be_true
|
||||
v2==v1 . should_be_true
|
||||
|
||||
group_builder.specify "Greater or equal of complex and complex" <|
|
||||
v1 = (Complex.new 3)
|
||||
|
@ -235,6 +235,8 @@ add_specs suite_builder =
|
||||
# no conversions needed when calling `exchange` methods
|
||||
nine . should_equal one
|
||||
|
||||
suite_builder.group "MultiValue Conversions" group_builder->
|
||||
|
||||
group_builder.specify "Requesting Text & Foo" <|
|
||||
check a (n : Text & Foo) = case a of
|
||||
0 -> n.foo
|
||||
|
Loading…
Reference in New Issue
Block a user