Equality with conversions (#9070)

This commit is contained in:
Jaroslav Tulach 2024-02-19 17:18:56 +01:00 committed by GitHub
parent 760afbc7f4
commit a664dd9d56
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 1107 additions and 472 deletions

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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();
}
}
}

View File

@ -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;
});

View File

@ -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(

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -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)) {

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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 * */

View File

@ -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() {

View File

@ -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

View File

@ -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));
}

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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));
}

View File

@ -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)

View File

@ -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