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 = Number.should_equal self that epsilon=0 frames_to_skip=0 =
matches = case that of matches = case that of
_ : Number -> self.equals that epsilon _ : Number -> self.equals that epsilon
_ -> False _ -> self==that
case matches of case matches of
True -> Spec_Result.Success True -> Spec_Result.Success
False -> False ->
@ -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 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 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. `that` by containing duplicates.
It will work on any collection which supports the methods 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 that extend existing number types with `Complex_Number`, `Rational_Number` and
make them behave as first class citizen numbers. make them behave as first class citizen numbers.
### Custom Equality
The `==` operator is special. A consistency with hash code is necessary to make
any Enso object behave correctly and work effectively in `Set` and `Map`
implementations. To guarantee such level of consistency there is a `Any.==`
definition providing _universal equality_ that **shall not be overriden**.
The `==` behavior is predefined for builtin types, atoms and other Enso objects.
In addition to that it remains possible to define own _comparators_, including a
comparator capable to work with already existing types. To create such
comparator define:
- conversion between existing type and the new type (as described in
[previous section](#type-ascriptions-and-operator-resolution))
- comparator (see documentation of `Ordering` type)
- define **two conversion method** that return the same comparator
To extend the previous definition of `Num` also for equality one might do for
example:
```ruby
type Num_Comparator
compare a:Num b:Num = # compare somehow
hash a:Num = # hash somehow
Num.from (that:Integer) = # convert somehow
Comparable.from (_:Num) = Num_Comparator
Comparable.from (_:Integer) = Num_Comparator
```
with such a structure the internal implementation of `Any.==` performs necessary
conversions of `Integer` argument in case the other argument is `Num` and
invokes the `Num_Comparator.compare` to handle the comparision.
A care must be taken to keep consistency between `hash` values of original and
converted types - e.g. hash of `n:Integer` and hash of `Num.from n` must be the
same (otherwise consistency required for `Set` and `Map` would be compromised).
### Precedence ### Precedence
Operator precedence in Enso is a collection of rules that reflect conventions Operator precedence in Enso is a collection of rules that reflect conventions

View File

@ -63,10 +63,13 @@ another.
## Multiple Dispatch ## Multiple Dispatch
It is an open question as to whether we want to support proper multiple dispatch Multiple dispatch is currently used for
in Enso. Multiple dispatch refers to the dynamic dispatch target being [binary operators](../syntax/functions.md#type-ascriptions-and-operator-resolution).
determined based not only on the type of the `this` argument, but the types of
the other arguments to the function. Supporting it for general functions remains an open question as to whether we
want to support proper multiple dispatch in Enso. Multiple dispatch refers to
the dynamic dispatch target being determined based not only on the type of the
`this` argument, but the types of the other arguments to the function.
To do multiple dispatch properly, it is very important to get a rigorous To do multiple dispatch properly, it is very important to get a rigorous
specification of the specificity algorithm. It must account for: specification of the specificity algorithm. It must account for:

View File

@ -75,6 +75,21 @@ public class EqualsBenchmarks {
new StringBuilder( new StringBuilder(
""" """
import Standard.Base.Data.Range.Extensions import Standard.Base.Data.Range.Extensions
import Standard.Base.Data.Numbers.Number
import Standard.Base.Data.Ordering.Ordering
import Standard.Base.Data.Ordering.Comparable
import Standard.Base.Data.Ordering.Default_Comparator
type Num
Value n
Num.from (that:Number) = Num.Value that
Comparable.from (_:Number) = Num_Comparator
Comparable.from (_:Num) = Num_Comparator
type Num_Comparator
compare x:Num y:Num = Ordering.compare x.n y.n
hash x:Num = Default_Comparator.hash x.n
type Node type Node
C1 f1 C1 f1
@ -87,7 +102,9 @@ public class EqualsBenchmarks {
eq_vec vec1 vec2 = eq_vec vec1 vec2 =
(0.up_to vec1.length).map idx-> (0.up_to vec1.length).map idx->
(vec1.at idx) == (vec2.at idx) v1 = vec1.at idx
v2 = vec2.at idx
v1 == v2
eq x y = x == y eq x y = x == y
"""); """);
@ -108,10 +125,37 @@ public class EqualsBenchmarks {
primitiveVectorSize / 64); primitiveVectorSize / 64);
codeBuilder codeBuilder
.append( .append(
generateVectorOfPrimitives(primitiveVectorSize, "vec1", 42, trueExpectedAt, random)) generateVectorOfPrimitives(
primitiveVectorSize, "vec1", 42, trueExpectedAt, random, "%d", "%f"))
.append("\n") .append("\n")
.append( .append(
generateVectorOfPrimitives(primitiveVectorSize, "vec2", 42, trueExpectedAt, random)) generateVectorOfPrimitives(
primitiveVectorSize, "vec2", 42, trueExpectedAt, random, "%d", "%f"))
.append("\n");
}
case "equalsWithConversion" -> {
trueExpectedAt =
Set.of(
primitiveVectorSize / 2,
primitiveVectorSize / 4,
primitiveVectorSize / 8,
primitiveVectorSize / 16,
primitiveVectorSize / 32,
primitiveVectorSize / 64);
codeBuilder
.append(
generateVectorOfPrimitives(
primitiveVectorSize, "vec1", 42, trueExpectedAt, random, "%d", "%f"))
.append("\n")
.append(
generateVectorOfPrimitives(
primitiveVectorSize,
"vec2",
42,
trueExpectedAt,
random,
"Num.Value %d",
"Num.Value %f"))
.append("\n"); .append("\n");
} }
case "equalsStrings" -> { case "equalsStrings" -> {
@ -141,7 +185,7 @@ public class EqualsBenchmarks {
} }
codeBuilder.append(""" codeBuilder.append("""
bench x = eq_vec vec1 vec2 bench _ = eq_vec vec1 vec2
"""); """);
module = ctx.eval(SrcUtil.source(benchmarkName, codeBuilder.toString())); module = ctx.eval(SrcUtil.source(benchmarkName, codeBuilder.toString()));
@ -171,6 +215,11 @@ public class EqualsBenchmarks {
performBenchmark(blackHole); performBenchmark(blackHole);
} }
@Benchmark
public void equalsWithConversion(Blackhole blackHole) {
performBenchmark(blackHole);
}
@Benchmark @Benchmark
public void equalsStrings(Blackhole blackhole) { public void equalsStrings(Blackhole blackhole) {
performBenchmark(blackhole); performBenchmark(blackhole);
@ -207,7 +256,9 @@ public class EqualsBenchmarks {
String vecName, String vecName,
Object identityElem, Object identityElem,
Collection<Integer> constantIdxs, Collection<Integer> constantIdxs,
Random random) { Random random,
String intFormat,
String doubleFormat) {
var partSize = totalSize / 2; var partSize = totalSize / 2;
List<Object> primitiveValues = new ArrayList<>(); List<Object> primitiveValues = new ArrayList<>();
random.ints(partSize).forEach(primitiveValues::add); random.ints(partSize).forEach(primitiveValues::add);
@ -221,9 +272,9 @@ public class EqualsBenchmarks {
sb.append(vecName).append(" = ["); sb.append(vecName).append(" = [");
for (Object primitiveValue : primitiveValues) { for (Object primitiveValue : primitiveValues) {
if (primitiveValue instanceof Double dbl) { if (primitiveValue instanceof Double dbl) {
sb.append(String.format("%f", dbl)).append(","); sb.append(String.format(doubleFormat, dbl)).append(",");
} else { } else {
sb.append(primitiveValue).append(","); sb.append(String.format(intFormat, primitiveValue)).append(",");
} }
} }
// Replace last comma // Replace last comma

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.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import com.oracle.truffle.api.frame.VirtualFrame;
import java.math.BigInteger; import java.math.BigInteger;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalTime; import java.time.LocalTime;
@ -16,7 +17,6 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode; import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode;
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode; import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
import org.enso.interpreter.node.expression.builtin.meta.EqualsNodeGen;
import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedConversion;
import org.enso.interpreter.runtime.number.EnsoBigInteger; import org.enso.interpreter.runtime.number.EnsoBigInteger;
import org.enso.polyglot.MethodNames; import org.enso.polyglot.MethodNames;
@ -44,8 +44,8 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
testRootNode = new TestRootNode(); testRootNode = new TestRootNode(EqualsTest::equalityCheck);
equalsNode = EqualsNode.build(); equalsNode = EqualsNode.create();
hostValueToEnsoNode = HostValueToEnsoNode.build(); hostValueToEnsoNode = HostValueToEnsoNode.build();
testRootNode.insertChildren(equalsNode, hostValueToEnsoNode); testRootNode.insertChildren(equalsNode, hostValueToEnsoNode);
return null; return null;
@ -88,13 +88,22 @@ public class EqualsTest extends TestBase {
} }
} }
private static boolean equalityCheck(VirtualFrame frame) {
var args = frame.getArguments();
return equalsNode.execute(frame, args[0], args[1]);
}
private boolean equalityCheck(Object first, Object second) {
return (Boolean) testRootNode.getCallTarget().call(first, second);
}
@Theory @Theory
public void equalsOperatorShouldBeSymmetric(Object firstValue, Object secondValue) { public void equalsOperatorShouldBeSymmetric(Object firstValue, Object secondValue) {
executeInContext( executeInContext(
context, context,
() -> { () -> {
boolean firstResult = equalsNode.execute(firstValue, secondValue); boolean firstResult = equalityCheck(firstValue, secondValue);
boolean secondResult = equalsNode.execute(secondValue, firstValue); boolean secondResult = equalityCheck(secondValue, firstValue);
assertEquals("equals should be symmetric", firstResult, secondResult); assertEquals("equals should be symmetric", firstResult, secondResult);
return null; return null;
}); });
@ -105,8 +114,8 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
Object firstResult = equalsNode.execute(value, value); Object firstResult = equalityCheck(value, value);
Object secondResult = equalsNode.execute(value, value); Object secondResult = equalityCheck(value, value);
assertEquals("equals should be consistent", firstResult, secondResult); assertEquals("equals should be consistent", firstResult, secondResult);
return null; return null;
}); });
@ -117,8 +126,8 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
Object uncachedRes = EqualsNodeGen.getUncached().execute(firstVal, secondVal); Object uncachedRes = EqualsNode.getUncached().execute(null, firstVal, secondVal);
Object cachedRes = equalsNode.execute(firstVal, secondVal); Object cachedRes = equalityCheck(firstVal, secondVal);
assertEquals( assertEquals(
"Result from uncached EqualsNode should be the same as result from its cached" "Result from uncached EqualsNode should be the same as result from its cached"
+ " variant", + " variant",
@ -140,7 +149,7 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue(equalsNode.execute(ensoDate, javaDate)); assertTrue(equalityCheck(ensoDate, javaDate));
return null; return null;
}); });
} }
@ -158,7 +167,7 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue(equalsNode.execute(ensoTime, javaDate)); assertTrue(equalityCheck(ensoTime, javaDate));
return null; return null;
}); });
} }
@ -181,7 +190,7 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue(equalsNode.execute(ensoDateTime, javaDateTime)); assertTrue(equalityCheck(ensoDateTime, javaDateTime));
return null; return null;
}); });
} }
@ -194,7 +203,7 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue(javaNumber + " == " + ensoNumber, equalsNode.execute(javaNumber, ensoNumber)); assertTrue(javaNumber + " == " + ensoNumber, equalityCheck(javaNumber, ensoNumber));
return null; return null;
}); });
} }
@ -207,7 +216,7 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue(ensoNumber + " == " + javaNumber, equalsNode.execute(ensoNumber, javaNumber)); assertTrue(ensoNumber + " == " + javaNumber, equalityCheck(ensoNumber, javaNumber));
return null; return null;
}); });
} }
@ -220,7 +229,7 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue(javaNumber + " == " + hostNumber, equalsNode.execute(javaNumber, hostNumber)); assertTrue(javaNumber + " == " + hostNumber, equalityCheck(javaNumber, hostNumber));
return null; return null;
}); });
} }
@ -233,7 +242,7 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue(hostNumber + " == " + javaNumber, equalsNode.execute(hostNumber, javaNumber)); assertTrue(hostNumber + " == " + javaNumber, equalityCheck(hostNumber, javaNumber));
return null; return null;
}); });
} }
@ -246,7 +255,7 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue(equalsNode.execute(ensoVector, javaVector)); assertTrue(equalityCheck(ensoVector, javaVector));
return null; return null;
}); });
} }
@ -258,9 +267,9 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue(equalsNode.execute(ensoNumber, foreignNumber.asDirect())); assertTrue(equalityCheck(ensoNumber, foreignNumber.asDirect()));
assertTrue(equalsNode.execute(ensoNumber, foreignNumber)); assertTrue(equalityCheck(ensoNumber, foreignNumber));
assertTrue(equalsNode.execute(foreignNumber, ensoNumber)); assertTrue(equalityCheck(foreignNumber, ensoNumber));
return null; return null;
}); });
} }
@ -272,9 +281,9 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue(equalsNode.execute(ensoNumber, foreignNumber.asDirect())); assertTrue(equalityCheck(ensoNumber, foreignNumber.asDirect()));
assertTrue(equalsNode.execute(ensoNumber, foreignNumber)); assertTrue(equalityCheck(ensoNumber, foreignNumber));
assertTrue(equalsNode.execute(foreignNumber, ensoNumber)); assertTrue(equalityCheck(foreignNumber, ensoNumber));
return null; return null;
}); });
} }
@ -287,8 +296,8 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue(equalsNode.execute(ensoNumber, foreignNumber)); assertTrue(equalityCheck(ensoNumber, foreignNumber));
assertTrue(equalsNode.execute(foreignNumber, ensoNumber)); assertTrue(equalityCheck(foreignNumber, ensoNumber));
return null; return null;
}); });
} }
@ -301,9 +310,9 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue(equalsNode.execute(ensoBoolean, foreignBoolean.asDirect())); assertTrue(equalityCheck(ensoBoolean, foreignBoolean.asDirect()));
assertTrue(equalsNode.execute(ensoBoolean, foreignBoolean)); assertTrue(equalityCheck(ensoBoolean, foreignBoolean));
assertTrue(equalsNode.execute(foreignBoolean, ensoBoolean)); assertTrue(equalityCheck(foreignBoolean, ensoBoolean));
return null; return null;
}); });
} }
@ -315,9 +324,9 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue(equalsNode.execute(ensoText, foreignString.asDirect())); assertTrue(equalityCheck(ensoText, foreignString.asDirect()));
assertTrue(equalsNode.execute(ensoText, foreignString)); assertTrue(equalityCheck(ensoText, foreignString));
assertTrue(equalsNode.execute(foreignString, ensoText)); assertTrue(equalityCheck(foreignString, ensoText));
return null; return null;
}); });
} }
@ -336,8 +345,8 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue(equalsNode.execute(142L, hundred42)); assertTrue(equalityCheck(142L, hundred42));
assertTrue(equalsNode.execute(hundred42, 142L)); assertTrue(equalityCheck(hundred42, 142L));
return null; return null;
}); });
} }
@ -375,15 +384,12 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue( assertTrue("Conversions from same module are the same", equalityCheck(conv1, conv1_2));
"Conversions from same module are the same", equalsNode.execute(conv1, conv1_2)); assertTrue("Conversions from same module are the same", equalityCheck(conv2, conv2_2));
assertTrue(
"Conversions from same module are the same", equalsNode.execute(conv2, conv2_2));
assertFalse( assertFalse(
"Conversions from other modules aren't the same", equalsNode.execute(conv1, conv2)); "Conversions from other modules aren't the same", equalityCheck(conv1, conv2));
assertFalse( assertFalse(
"Conversions from other modueles aren't the same", "Conversions from other modueles aren't the same", equalityCheck(conv2_2, conv1_2));
equalsNode.execute(conv2_2, conv1_2));
return null; return null;
}); });

View File

@ -38,7 +38,7 @@ public class HashCodeTest extends TestBase {
context, context,
() -> { () -> {
hashCodeNode = HashCodeNode.build(); hashCodeNode = HashCodeNode.build();
equalsNode = EqualsNode.build(); equalsNode = EqualsNode.create();
hostValueToEnsoNode = HostValueToEnsoNode.build(); hostValueToEnsoNode = HostValueToEnsoNode.build();
testRootNode = new TestRootNode(); testRootNode = new TestRootNode();
testRootNode.insertChildren(hashCodeNode, equalsNode, hostValueToEnsoNode); testRootNode.insertChildren(hashCodeNode, equalsNode, hostValueToEnsoNode);
@ -94,7 +94,7 @@ public class HashCodeTest extends TestBase {
() -> { () -> {
long firstHash = hashCodeNode.execute(firstValue); long firstHash = hashCodeNode.execute(firstValue);
long secondHash = hashCodeNode.execute(secondValue); long secondHash = hashCodeNode.execute(secondValue);
Object valuesAreEqual = equalsNode.execute(firstValue, secondValue); Object valuesAreEqual = equalsNode.execute(null, firstValue, secondValue);
// if o1 == o2 then hash(o1) == hash(o2) // if o1 == o2 then hash(o1) == hash(o2)
if (isTrue(valuesAreEqual)) { if (isTrue(valuesAreEqual)) {
assertEquals( assertEquals(

View File

@ -13,6 +13,7 @@ import java.io.OutputStream;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.function.Function;
import java.util.logging.Level; import java.util.logging.Level;
import org.enso.interpreter.EnsoLanguage; import org.enso.interpreter.EnsoLanguage;
import org.enso.polyglot.MethodNames.Module; import org.enso.polyglot.MethodNames.Module;
@ -45,6 +46,7 @@ public abstract class TestBase {
.allowIO(IOAccess.ALL) .allowIO(IOAccess.ALL)
.allowAllAccess(true) .allowAllAccess(true)
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName()) .option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
.option(RuntimeOptions.DISABLE_IR_CACHES, "true")
.logHandler(System.err) .logHandler(System.err)
.option(RuntimeOptions.STRICT_ERRORS, "true") .option(RuntimeOptions.STRICT_ERRORS, "true")
.option( .option(
@ -152,8 +154,15 @@ public abstract class TestBase {
* #insertChildren(Node...)}. * #insertChildren(Node...)}.
*/ */
static class TestRootNode extends RootNode { static class TestRootNode extends RootNode {
private final Function<VirtualFrame, Object> callback;
TestRootNode() { TestRootNode() {
this(null);
}
TestRootNode(Function<VirtualFrame, Object> callback) {
super(EnsoLanguage.get(null)); super(EnsoLanguage.get(null));
this.callback = callback;
} }
void insertChildren(Node... children) { void insertChildren(Node... children) {
@ -165,7 +174,11 @@ public abstract class TestBase {
/** In the tests, do not execute this root node, but execute directly the child nodes. */ /** In the tests, do not execute this root node, but execute directly the child nodes. */
@Override @Override
public Object execute(VirtualFrame frame) { public Object execute(VirtualFrame frame) {
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.Cached.Shared;
import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.Node;
@ -32,7 +34,7 @@ public abstract class EqualsAtomNode extends Node {
return EqualsAtomNodeGen.create(); return EqualsAtomNodeGen.create();
} }
public abstract boolean execute(Atom left, Atom right); public abstract boolean execute(VirtualFrame frame, Atom left, Atom right);
static EqualsNode[] createEqualsNodes(int size) { static EqualsNode[] createEqualsNodes(int size) {
EqualsNode[] nodes = new EqualsNode[size]; EqualsNode[] nodes = new EqualsNode[size];
@ -48,6 +50,7 @@ public abstract class EqualsAtomNode extends Node {
limit = "10") limit = "10")
@ExplodeLoop @ExplodeLoop
boolean equalsAtomsWithDefaultComparator( boolean equalsAtomsWithDefaultComparator(
VirtualFrame frame,
Atom self, Atom self,
Atom other, Atom other,
@Cached("self.getConstructor()") AtomConstructor selfCtorCached, @Cached("self.getConstructor()") AtomConstructor selfCtorCached,
@ -65,7 +68,7 @@ public abstract class EqualsAtomNode extends Node {
for (int i = 0; i < fieldsLenCached; i++) { for (int i = 0; i < fieldsLenCached; i++) {
var selfValue = structsLib.getField(self, i); var selfValue = structsLib.getField(self, i);
var otherValue = structsLib.getField(other, i); var otherValue = structsLib.getField(other, i);
var fieldsAreEqual = fieldEqualsNodes[i].execute(selfValue, otherValue); var fieldsAreEqual = fieldEqualsNodes[i].execute(frame, selfValue, otherValue);
if (!fieldsAreEqual) { if (!fieldsAreEqual) {
return false; return false;
} }
@ -116,13 +119,18 @@ public abstract class EqualsAtomNode extends Node {
} }
} }
@CompilerDirectives.TruffleBoundary
@Specialization( @Specialization(
replaces = {"equalsAtomsWithDefaultComparator", "equalsAtomsWithCustomComparator"}) replaces = {"equalsAtomsWithDefaultComparator", "equalsAtomsWithCustomComparator"})
boolean equalsAtomsUncached(Atom self, Atom other) { boolean equalsAtomsUncached(VirtualFrame frame, Atom self, Atom other) {
if (self.getConstructor() != other.getConstructor()) { if (self.getConstructor() != other.getConstructor()) {
return false; return false;
} else {
return equalsAtomsUncached(frame == null ? null : frame.materialize(), self, other);
} }
}
@CompilerDirectives.TruffleBoundary
private boolean equalsAtomsUncached(MaterializedFrame frame, Atom self, Atom other) {
Type customComparator = CustomComparatorNode.getUncached().execute(self); Type customComparator = CustomComparatorNode.getUncached().execute(self);
if (customComparator != null) { if (customComparator != null) {
Function compareFunc = findCompareMethod(customComparator); Function compareFunc = findCompareMethod(customComparator);
@ -139,7 +147,7 @@ public abstract class EqualsAtomNode extends Node {
for (int i = 0; i < self.getConstructor().getArity(); i++) { for (int i = 0; i < self.getConstructor().getArity(); i++) {
var selfField = StructsLibrary.getUncached().getField(self, i); var selfField = StructsLibrary.getUncached().getField(self, i);
var otherField = StructsLibrary.getUncached().getField(other, i); var otherField = StructsLibrary.getUncached().getField(other, i);
boolean areFieldsSame = EqualsNodeGen.getUncached().execute(selfField, otherField); boolean areFieldsSame = EqualsNode.getUncached().execute(frame, selfField, otherField);
if (!areFieldsSame) { if (!areFieldsSame) {
return false; return false;
} }

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.Cached.Shared;
import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.ArityException; import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException; import com.oracle.truffle.api.interop.InvalidArrayIndexException;
@ -18,9 +19,7 @@ import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.Node;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import org.enso.interpreter.dsl.AcceptsError;
import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode; import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode;
import org.enso.interpreter.node.expression.builtin.ordering.CustomComparatorNode;
import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.Module; import org.enso.interpreter.runtime.Module;
import org.enso.interpreter.runtime.callable.UnresolvedConversion; import org.enso.interpreter.runtime.callable.UnresolvedConversion;
@ -39,24 +38,26 @@ public abstract class EqualsComplexNode extends Node {
return EqualsComplexNodeGen.create(); return EqualsComplexNodeGen.create();
} }
public abstract boolean execute(@AcceptsError Object left, @AcceptsError Object right); public abstract boolean execute(VirtualFrame frame, Object left, Object right);
/** Enso specific types */ /** Enso specific types */
@Specialization @Specialization
boolean equalsUnresolvedSymbols( boolean equalsUnresolvedSymbols(
VirtualFrame frame,
UnresolvedSymbol self, UnresolvedSymbol self,
UnresolvedSymbol otherSymbol, UnresolvedSymbol otherSymbol,
@Shared("equalsNode") @Cached EqualsNode equalsNode) { @Shared("equalsNode") @Cached EqualsNode equalsNode) {
return self.getName().equals(otherSymbol.getName()) return self.getName().equals(otherSymbol.getName())
&& equalsNode.execute(self.getScope(), otherSymbol.getScope()); && equalsNode.execute(frame, self.getScope(), otherSymbol.getScope());
} }
@Specialization @Specialization
boolean equalsUnresolvedConversion( boolean equalsUnresolvedConversion(
VirtualFrame frame,
UnresolvedConversion selfConversion, UnresolvedConversion selfConversion,
UnresolvedConversion otherConversion, UnresolvedConversion otherConversion,
@Shared("equalsNode") @Cached EqualsNode equalsNode) { @Shared("equalsNode") @Cached EqualsNode equalsNode) {
return equalsNode.execute(selfConversion.getScope(), otherConversion.getScope()); return equalsNode.execute(frame, selfConversion.getScope(), otherConversion.getScope());
} }
@Specialization @Specialization
@ -71,8 +72,11 @@ public abstract class EqualsComplexNode extends Node {
@Specialization @Specialization
boolean equalsFiles( boolean equalsFiles(
EnsoFile selfFile, EnsoFile otherFile, @Shared("equalsNode") @Cached EqualsNode equalsNode) { VirtualFrame frame,
return equalsNode.execute(selfFile.getPath(), otherFile.getPath()); EnsoFile selfFile,
EnsoFile otherFile,
@Shared("equalsNode") @Cached EqualsNode equalsNode) {
return equalsNode.execute(frame, selfFile.getPath(), otherFile.getPath());
} }
/** /**
@ -82,12 +86,13 @@ public abstract class EqualsComplexNode extends Node {
*/ */
@Specialization(guards = {"typesLib.hasType(selfType)", "typesLib.hasType(otherType)"}) @Specialization(guards = {"typesLib.hasType(selfType)", "typesLib.hasType(otherType)"})
boolean equalsTypes( boolean equalsTypes(
VirtualFrame frame,
Type selfType, Type selfType,
Type otherType, Type otherType,
@Shared("equalsNode") @Cached EqualsNode equalsNode, @Shared("equalsNode") @Cached EqualsNode equalsNode,
@Shared("typesLib") @CachedLibrary(limit = "10") TypesLibrary typesLib) { @Shared("typesLib") @CachedLibrary(limit = "10") TypesLibrary typesLib) {
return equalsNode.execute( return equalsNode.execute(
selfType.getQualifiedName().toString(), otherType.getQualifiedName().toString()); frame, selfType.getQualifiedName().toString(), otherType.getQualifiedName().toString());
} }
/** /**
@ -99,6 +104,7 @@ public abstract class EqualsComplexNode extends Node {
}, },
limit = "3") limit = "3")
boolean equalsWithWarnings( boolean equalsWithWarnings(
VirtualFrame frame,
Object selfWithWarnings, Object selfWithWarnings,
Object otherWithWarnings, Object otherWithWarnings,
@CachedLibrary("selfWithWarnings") WarningsLibrary selfWarnLib, @CachedLibrary("selfWithWarnings") WarningsLibrary selfWarnLib,
@ -113,7 +119,7 @@ public abstract class EqualsComplexNode extends Node {
otherWarnLib.hasWarnings(otherWithWarnings) otherWarnLib.hasWarnings(otherWithWarnings)
? otherWarnLib.removeWarnings(otherWithWarnings) ? otherWarnLib.removeWarnings(otherWithWarnings)
: otherWithWarnings; : otherWithWarnings;
return equalsNode.execute(self, other); return equalsNode.execute(frame, self, other);
} catch (UnsupportedMessageException e) { } catch (UnsupportedMessageException e) {
throw EnsoContext.get(this).raiseAssertionPanic(this, null, e); throw EnsoContext.get(this).raiseAssertionPanic(this, null, e);
} }
@ -278,12 +284,12 @@ public abstract class EqualsComplexNode extends Node {
}, },
limit = "3") limit = "3")
boolean equalsArrays( boolean equalsArrays(
VirtualFrame frame,
Object selfArray, Object selfArray,
Object otherArray, Object otherArray,
@CachedLibrary("selfArray") InteropLibrary selfInterop, @CachedLibrary("selfArray") InteropLibrary selfInterop,
@CachedLibrary("otherArray") InteropLibrary otherInterop, @CachedLibrary("otherArray") InteropLibrary otherInterop,
@Shared("equalsNode") @Cached EqualsNode equalsNode, @Shared("equalsNode") @Cached EqualsNode equalsNode,
@Cached CustomComparatorNode hasCustomComparatorNode,
@Shared("hostValueToEnsoNode") @Cached HostValueToEnsoNode valueToEnsoNode) { @Shared("hostValueToEnsoNode") @Cached HostValueToEnsoNode valueToEnsoNode) {
try { try {
long selfSize = selfInterop.getArraySize(selfArray); long selfSize = selfInterop.getArraySize(selfArray);
@ -293,7 +299,7 @@ public abstract class EqualsComplexNode extends Node {
for (long i = 0; i < selfSize; i++) { for (long i = 0; i < selfSize; i++) {
Object selfElem = valueToEnsoNode.execute(selfInterop.readArrayElement(selfArray, i)); Object selfElem = valueToEnsoNode.execute(selfInterop.readArrayElement(selfArray, i));
Object otherElem = valueToEnsoNode.execute(otherInterop.readArrayElement(otherArray, i)); Object otherElem = valueToEnsoNode.execute(otherInterop.readArrayElement(otherArray, i));
boolean elemsAreEqual = equalsNode.execute(selfElem, otherElem); boolean elemsAreEqual = equalsNode.execute(frame, selfElem, otherElem);
if (!elemsAreEqual) { if (!elemsAreEqual) {
return false; return false;
} }
@ -313,6 +319,7 @@ public abstract class EqualsComplexNode extends Node {
}, },
limit = "3") limit = "3")
boolean equalsHashMaps( boolean equalsHashMaps(
VirtualFrame frame,
Object selfHashMap, Object selfHashMap,
Object otherHashMap, Object otherHashMap,
@CachedLibrary("selfHashMap") InteropLibrary selfInterop, @CachedLibrary("selfHashMap") InteropLibrary selfInterop,
@ -337,7 +344,7 @@ public abstract class EqualsComplexNode extends Node {
&& otherInterop.isHashEntryReadable(otherHashMap, key)) { && otherInterop.isHashEntryReadable(otherHashMap, key)) {
Object otherValue = Object otherValue =
valueToEnsoNode.execute(otherInterop.readHashValue(otherHashMap, key)); valueToEnsoNode.execute(otherInterop.readHashValue(otherHashMap, key));
if (!equalsNode.execute(selfValue, otherValue)) { if (!equalsNode.execute(frame, selfValue, otherValue)) {
return false; return false;
} }
} else { } else {
@ -391,13 +398,14 @@ public abstract class EqualsComplexNode extends Node {
// It has well-defined equality based on the qualified name. // It has well-defined equality based on the qualified name.
@Specialization(guards = {"isJavaFunction(selfHostFunc)", "isJavaFunction(otherHostFunc)"}) @Specialization(guards = {"isJavaFunction(selfHostFunc)", "isJavaFunction(otherHostFunc)"})
boolean equalsHostFunctions( boolean equalsHostFunctions(
VirtualFrame frame,
Object selfHostFunc, Object selfHostFunc,
Object otherHostFunc, Object otherHostFunc,
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop, @Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop,
@Shared("equalsNode") @Cached EqualsNode equalsNode) { @Shared("equalsNode") @Cached EqualsNode equalsNode) {
Object selfFuncStrRepr = interop.toDisplayString(selfHostFunc); Object selfFuncStrRepr = interop.toDisplayString(selfHostFunc);
Object otherFuncStrRepr = interop.toDisplayString(otherHostFunc); Object otherFuncStrRepr = interop.toDisplayString(otherHostFunc);
return equalsNode.execute(selfFuncStrRepr, otherFuncStrRepr); return equalsNode.execute(frame, selfFuncStrRepr, otherFuncStrRepr);
} }
@Specialization(guards = "fallbackGuard(left, right, interop, warningsLib)") @Specialization(guards = "fallbackGuard(left, right, interop, warningsLib)")
@ -419,7 +427,8 @@ public abstract class EqualsComplexNode extends Node {
// all the other guards on purpose. // all the other guards on purpose.
boolean fallbackGuard( boolean fallbackGuard(
Object left, Object right, InteropLibrary interop, WarningsLibrary warnings) { Object left, Object right, InteropLibrary interop, WarningsLibrary warnings) {
if (EqualsNode.isPrimitive(left, interop) && EqualsNode.isPrimitive(right, interop)) { if (EqualsSimpleNode.isPrimitive(left, interop)
&& EqualsSimpleNode.isPrimitive(right, interop)) {
return false; return false;
} }
if (isJavaObject(left) && isJavaObject(right)) { if (isJavaObject(left) && isJavaObject(right)) {

View File

@ -1,27 +1,29 @@
package org.enso.interpreter.node.expression.builtin.meta; package org.enso.interpreter.node.expression.builtin.meta;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Cached.Shared; import com.oracle.truffle.api.dsl.Cached.Shared;
import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.NeverDefault;
import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.Node;
import java.math.BigInteger;
import org.enso.interpreter.dsl.AcceptsError; import org.enso.interpreter.dsl.AcceptsError;
import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps; import org.enso.interpreter.node.EnsoRootNode;
import org.enso.interpreter.node.callable.InteropConversionCallNode;
import org.enso.interpreter.node.callable.InvokeCallableNode.ArgumentsExecutionMode;
import org.enso.interpreter.node.callable.InvokeCallableNode.DefaultsExecutionMode;
import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode;
import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.data.EnsoMultiValue; import org.enso.interpreter.runtime.callable.UnresolvedConversion;
import org.enso.interpreter.runtime.data.atom.Atom; import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.data.atom.AtomConstructor; import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.data.Type;
import org.enso.interpreter.runtime.error.WarningsLibrary; import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.number.EnsoBigInteger; import org.enso.interpreter.runtime.scope.ModuleScope;
import org.enso.polyglot.common_utils.Core_Text_Utils; import org.enso.interpreter.runtime.state.State;
@BuiltinMethod( @BuiltinMethod(
type = "Any", type = "Any",
@ -40,367 +42,252 @@ import org.enso.polyglot.common_utils.Core_Text_Utils;
references point to the same object on the heap. Moreover, `Meta.is_same_object` references point to the same object on the heap. Moreover, `Meta.is_same_object`
implies `Any.==` for all object with the exception of `Number.nan`. implies `Any.==` for all object with the exception of `Number.nan`.
""") """)
@GenerateUncached public final class EqualsNode extends Node {
public abstract class EqualsNode extends Node { @Child private EqualsSimpleNode node;
@Child private TypeOfNode types;
@Child private WithConversionNode convert;
public static EqualsNode build() { private static final EqualsNode UNCACHED =
return EqualsNodeGen.create(); new EqualsNode(EqualsSimpleNodeGen.getUncached(), TypeOfNode.getUncached(), true);
}
public abstract boolean execute(@AcceptsError Object self, @AcceptsError Object right); private EqualsNode(EqualsSimpleNode node, TypeOfNode types, boolean uncached) {
this.node = node;
@Specialization this.types = types;
boolean equalsBoolBool(boolean self, boolean other) { if (uncached) {
return self == other; convert = EqualsNodeFactory.WithConversionNodeGen.getUncached();
}
@Specialization
boolean equalsBoolDouble(boolean self, double other) {
return false;
}
@Specialization
boolean equalsBoolLong(boolean self, long other) {
return false;
}
@Specialization
boolean equalsBoolBigInt(boolean self, EnsoBigInteger other) {
return false;
}
@Specialization
boolean equalsBoolText(boolean self, Text other) {
return false;
}
@Specialization
boolean equalsBoolInterop(
boolean self,
Object other,
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop) {
try {
return self == interop.asBoolean(other);
} catch (UnsupportedMessageException ex) {
return false;
} }
} }
@Specialization @NeverDefault
boolean equalsLongLong(long self, long other) { static EqualsNode build() {
return self == other; return create();
} }
@Specialization @NeverDefault
boolean equalsLongBool(long self, boolean other) { public static EqualsNode create() {
return false; return new EqualsNode(EqualsSimpleNode.build(), TypeOfNode.build(), false);
} }
@Specialization @NeverDefault
boolean equalsLongDouble(long self, double other) { public static EqualsNode getUncached() {
return (double) self == other; return UNCACHED;
} }
@Specialization /**
boolean equalsLongText(long self, Text other) { * Compares two objects for equality. If the {@link EqualsSimpleNode simple check} fails, it tries
return false; * to convert first argument to the second one and compare again.
} *
* @param frame the stack frame we are executing at
@Specialization * @param self the self object
@TruffleBoundary * @param other the other object
boolean equalsLongBigInt(long self, EnsoBigInteger other) { * @return {@code true} if {@code self} and {@code that} seem equal
if (BigIntegerOps.fitsInLong(other.getValue())) { */
return BigInteger.valueOf(self).compareTo(other.getValue()) == 0; public boolean execute(
} else { VirtualFrame frame, @AcceptsError Object self, @AcceptsError Object other) {
return false; var areEqual = node.execute(frame, self, other);
} if (!areEqual) {
} var selfType = types.execute(self);
var otherType = types.execute(other);
@Specialization if (selfType != otherType) {
boolean equalsLongInterop( if (convert == null) {
long self, CompilerDirectives.transferToInterpreter();
Object other, convert = insert(WithConversionNode.create());
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop) { }
try { return convert.executeWithConversion(frame, other, self);
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;
} }
} return areEqual;
@TruffleBoundary
private static double asDouble(BigInteger big) {
return big.doubleValue();
}
@Specialization(guards = {"isBigInteger(iop, self)", "isBigInteger(iop, other)"})
@TruffleBoundary
boolean other(
Object self,
Object other,
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
return asBigInteger(iop, self).equals(asBigInteger(iop, other));
}
@Specialization(guards = "isBigInteger(iop, self)")
@TruffleBoundary
boolean equalsBigIntDouble(
Object self,
double other,
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
return asBigInteger(iop, self).doubleValue() == other;
}
@Specialization(guards = "isBigInteger(iop, self)")
@TruffleBoundary
boolean equalsBigIntLong(
Object self, long other, @Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
var v = asBigInteger(iop, self);
if (BigIntegerOps.fitsInLong(v)) {
return v.compareTo(BigInteger.valueOf(other)) == 0;
} else {
return false;
}
}
@Specialization
boolean equalsBigIntBool(EnsoBigInteger self, boolean other) {
return false;
}
@Specialization
boolean equalsBigIntText(EnsoBigInteger self, Text other) {
return false;
}
@TruffleBoundary
@Specialization(guards = {"isBigInteger(iop, self)", "!isPrimitiveValue(other)"})
boolean equalsBigIntInterop(
Object self,
Object other,
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary iop) {
try {
var otherBigInteger = InteropLibrary.getUncached().asBigInteger(other);
return asBigInteger(iop, self).equals(otherBigInteger);
} catch (UnsupportedMessageException ex) {
return false;
}
}
@Specialization(guards = {"selfText.is_normalized()", "otherText.is_normalized()"})
boolean equalsTextText(Text selfText, Text otherText) {
return selfText.toString().compareTo(otherText.toString()) == 0;
}
@Specialization
boolean equalsTextBool(Text self, boolean other) {
return false;
}
@Specialization
boolean equalsTextLong(Text selfText, long otherLong) {
return false;
}
@Specialization
boolean equalsTextDouble(Text selfText, double otherDouble) {
return false;
}
@Specialization
boolean equalsTextBigInt(Text self, EnsoBigInteger other) {
return false;
} }
/** /**
* Compares interop string with other object. If the other object doesn't support conversion to * A node that checks the type of {@code that} argument, performs conversion of {@code self} to
* String, it is not equal. Otherwise the two strings are compared according to the * {@code that} type, and executes equality check again.
* lexicographical order, handling Unicode normalization. See {@code Text_Utils.compare_to}.
*/ */
@TruffleBoundary @GenerateUncached
@Specialization( abstract static class WithConversionNode extends Node {
guards = {"selfInterop.isString(selfString)"},
limit = "3") @NeverDefault
boolean equalsStrings( static WithConversionNode create() {
Object selfString, return EqualsNodeFactory.WithConversionNodeGen.create();
Object otherString, }
@CachedLibrary("selfString") InteropLibrary selfInterop,
@CachedLibrary("otherString") InteropLibrary otherInterop) { /**
String selfJavaString; * @return {code false} if the conversion makes no sense or result of equality check after doing
String otherJavaString; * the conversion
try { */
selfJavaString = selfInterop.asString(selfString); abstract boolean executeWithConversion(VirtualFrame frame, Object self, Object that);
otherJavaString = otherInterop.asString(otherString);
} catch (UnsupportedMessageException e) { 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 false;
} }
return Core_Text_Utils.equals(selfJavaString, otherJavaString);
}
@Specialization(guards = "isPrimitive(self, interop) != isPrimitive(other, interop)") private boolean doDispatch(
boolean equalsDifferent( VirtualFrame frame,
Object self, Object self,
Object other, Object that,
@Shared("interop") @CachedLibrary(limit = "10") InteropLibrary interop) { Type selfType,
return false; InteropConversionCallNode convertNode,
} EqualsSimpleNode equalityNode)
throws PanicException {
var convert = UnresolvedConversion.build(selfType.getDefinitionScope());
/** Equals for Atoms and AtomConstructors */ var ctx = EnsoContext.get(this);
@Specialization var state = State.create(ctx);
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 {
try { try {
return iop.asBigInteger(v); var thatAsSelf = convertNode.execute(convert, state, new Object[] {selfType, that});
} catch (UnsupportedMessageException ex) { var result = equalityNode.execute(frame, self, thatAsSelf);
throw EnsoContext.get(this).raiseAssertionPanic(this, "Expecting BigInteger", ex); assert !result || assertHashCodeIsTheSame(that, thatAsSelf);
return result;
} catch (ArityException ex) {
var assertsOn = false;
assert assertsOn = true;
if (assertsOn) {
throw new AssertionError("Unexpected arity exception", ex);
}
return false;
} catch (PanicException ex) {
if (ctx.getBuiltins().error().isNoSuchConversionError(ex.getPayload())) {
return false;
}
throw ex;
} }
} }
private boolean assertHashCodeIsTheSame(Object self, Object converted) {
var selfHash = HashCodeNode.getUncached().execute(self);
var convertedHash = HashCodeNode.getUncached().execute(converted);
var ok = selfHash == convertedHash;
if (!ok) {
var msg =
"Different hash code! Original "
+ self
+ "[#"
+ Long.toHexString(selfHash)
+ "] got converted to "
+ converted
+ "[#"
+ Long.toHexString(convertedHash)
+ "]";
var ctx = EnsoContext.get(this);
throw ctx.raiseAssertionPanic(this, msg, new AssertionError(msg));
}
return ok;
}
} }
} }

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(); return HashCodeNodeGen.create();
} }
static HashCodeNode getUncached() {
return HashCodeNodeGen.getUncached();
}
public abstract long execute(@AcceptsError Object object); public abstract long execute(@AcceptsError Object object);
/** Specializations for primitive values * */ /** Specializations for primitive values * */

View File

@ -1,5 +1,6 @@
package org.enso.interpreter.node.expression.builtin.ordering; package org.enso.interpreter.node.expression.builtin.ordering;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.AcceptsError; import org.enso.interpreter.dsl.AcceptsError;
import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.dsl.BuiltinMethod;
@ -11,6 +12,7 @@ public final class SortArrayNode extends Node {
@Child private SortVectorNode sortVectorNode = SortVectorNode.build(); @Child private SortVectorNode sortVectorNode = SortVectorNode.build();
public Object execute( public Object execute(
VirtualFrame frame,
State state, State state,
@AcceptsError Object self, @AcceptsError Object self,
long ascending, long ascending,
@ -20,7 +22,15 @@ public final class SortArrayNode extends Node {
Object onFunc, Object onFunc,
long problemBehavior) { long problemBehavior) {
return sortVectorNode.execute( return sortVectorNode.execute(
state, self, ascending, comparators, compareFunctions, byFunc, onFunc, problemBehavior); frame,
state,
self,
ascending,
comparators,
compareFunctions,
byFunc,
onFunc,
problemBehavior);
} }
public static SortArrayNode build() { public static SortArrayNode build() {

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.Cached.Shared;
import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException; import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.interop.UnsupportedMessageException;
@ -77,6 +79,7 @@ public abstract class SortVectorNode extends Node {
* @return A new, sorted vector. * @return A new, sorted vector.
*/ */
public abstract Object execute( public abstract Object execute(
VirtualFrame frame,
State state, State state,
@AcceptsError Object self, @AcceptsError Object self,
long ascending, long ascending,
@ -101,6 +104,7 @@ public abstract class SortVectorNode extends Node {
"interop.isNull(onFunc)" "interop.isNull(onFunc)"
}) })
Object sortPrimitives( Object sortPrimitives(
VirtualFrame frame,
State state, State state,
Object self, Object self,
long ascending, long ascending,
@ -131,7 +135,14 @@ public abstract class SortVectorNode extends Node {
} }
var javaComparator = var javaComparator =
createDefaultComparator( createDefaultComparator(
lessThanNode, equalsNode, typeOfNode, toTextNode, ascending, problemBehavior, interop); frame.materialize(),
lessThanNode,
equalsNode,
typeOfNode,
toTextNode,
ascending,
problemBehavior,
interop);
try { try {
return sortPrimitiveVector(elems, javaComparator); return sortPrimitiveVector(elems, javaComparator);
} catch (CompareException e) { } catch (CompareException e) {
@ -142,6 +153,7 @@ public abstract class SortVectorNode extends Node {
@TruffleBoundary @TruffleBoundary
private DefaultSortComparator createDefaultComparator( private DefaultSortComparator createDefaultComparator(
MaterializedFrame frame,
LessThanNode lessThanNode, LessThanNode lessThanNode,
EqualsNode equalsNode, EqualsNode equalsNode,
TypeOfNode typeOfNode, TypeOfNode typeOfNode,
@ -150,6 +162,7 @@ public abstract class SortVectorNode extends Node {
long problemBehaviorNum, long problemBehaviorNum,
InteropLibrary interop) { InteropLibrary interop) {
return new DefaultSortComparator( return new DefaultSortComparator(
frame,
lessThanNode, lessThanNode,
equalsNode, equalsNode,
typeOfNode, typeOfNode,
@ -165,6 +178,7 @@ public abstract class SortVectorNode extends Node {
"interop.hasArrayElements(self)", "interop.hasArrayElements(self)",
}) })
Object sortGeneric( Object sortGeneric(
MaterializedFrame frame,
State state, State state,
Object self, Object self,
long ascending, long ascending,
@ -206,6 +220,7 @@ public abstract class SortVectorNode extends Node {
if (interop.isNull(byFunc) && interop.isNull(onFunc) && isPrimitiveGroup(group)) { if (interop.isNull(byFunc) && interop.isNull(onFunc) && isPrimitiveGroup(group)) {
javaComparator = javaComparator =
new DefaultSortComparator( new DefaultSortComparator(
frame,
lessThanNode, lessThanNode,
equalsNode, equalsNode,
typeOfNode, typeOfNode,
@ -557,12 +572,14 @@ public abstract class SortVectorNode extends Node {
*/ */
final class DefaultSortComparator extends SortComparator { final class DefaultSortComparator extends SortComparator {
private final MaterializedFrame frame;
private final LessThanNode lessThanNode; private final LessThanNode lessThanNode;
private final EqualsNode equalsNode; private final EqualsNode equalsNode;
private final TypeOfNode typeOfNode; private final TypeOfNode typeOfNode;
private final boolean ascending; private final boolean ascending;
private DefaultSortComparator( private DefaultSortComparator(
MaterializedFrame frame,
LessThanNode lessThanNode, LessThanNode lessThanNode,
EqualsNode equalsNode, EqualsNode equalsNode,
TypeOfNode typeOfNode, TypeOfNode typeOfNode,
@ -571,6 +588,7 @@ public abstract class SortVectorNode extends Node {
ProblemBehavior problemBehavior, ProblemBehavior problemBehavior,
InteropLibrary interop) { InteropLibrary interop) {
super(toTextNode, problemBehavior, interop); super(toTextNode, problemBehavior, interop);
this.frame = frame;
this.lessThanNode = lessThanNode; this.lessThanNode = lessThanNode;
this.equalsNode = equalsNode; this.equalsNode = equalsNode;
this.typeOfNode = typeOfNode; this.typeOfNode = typeOfNode;
@ -583,7 +601,7 @@ public abstract class SortVectorNode extends Node {
} }
int compareValuesWithDefaultComparator(Object x, Object y) { int compareValuesWithDefaultComparator(Object x, Object y) {
if (equalsNode.execute(x, y)) { if (equalsNode.execute(frame, x, y)) {
return 0; return 0;
} else { } else {
// Check if x < y // Check if x < y

View File

@ -369,7 +369,7 @@ public final class EnsoMultiValue implements EnsoObject {
try { try {
var members = iop.getMembers(values[i]); var members = iop.getMembers(values[i]);
var len = iop.getArraySize(members); var len = iop.getArraySize(members);
for (var j = 0L; j < len; i++) { for (var j = 0L; j < len; j++) {
var name = iop.readArrayElement(members, j); var name = iop.readArrayElement(members, j);
names.add(iop.asString(name)); names.add(iop.asString(name));
} }

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.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Cached.Shared; import com.oracle.truffle.api.dsl.Cached.Shared;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnknownKeyException; import com.oracle.truffle.api.interop.UnknownKeyException;
import com.oracle.truffle.api.interop.UnsupportedMessageException; import com.oracle.truffle.api.interop.UnsupportedMessageException;
@ -55,11 +56,11 @@ public final class EnsoHashMap implements EnsoObject {
} }
EnsoHashMapBuilder getMapBuilder( EnsoHashMapBuilder getMapBuilder(
boolean readOnly, HashCodeNode hashCodeNode, EqualsNode equalsNode) { VirtualFrame frame, boolean readOnly, HashCodeNode hashCodeNode, EqualsNode equalsNode) {
if (readOnly) { if (readOnly) {
return mapBuilder; return mapBuilder;
} else { } else {
return mapBuilder.asModifiable(generation, hashCodeNode, equalsNode); return mapBuilder.asModifiable(frame, generation, hashCodeNode, equalsNode);
} }
} }
@ -104,7 +105,7 @@ public final class EnsoHashMap implements EnsoObject {
Object key, Object key,
@Shared("hash") @Cached HashCodeNode hashCodeNode, @Shared("hash") @Cached HashCodeNode hashCodeNode,
@Shared("equals") @Cached EqualsNode equalsNode) { @Shared("equals") @Cached EqualsNode equalsNode) {
var entry = mapBuilder.get(key, generation, hashCodeNode, equalsNode); var entry = mapBuilder.get(null, key, generation, hashCodeNode, equalsNode);
return entry != null; return entry != null;
} }
@ -122,7 +123,7 @@ public final class EnsoHashMap implements EnsoObject {
@Shared("hash") @Cached HashCodeNode hashCodeNode, @Shared("hash") @Cached HashCodeNode hashCodeNode,
@Shared("equals") @Cached EqualsNode equalsNode) @Shared("equals") @Cached EqualsNode equalsNode)
throws UnknownKeyException { throws UnknownKeyException {
StorageEntry entry = mapBuilder.get(key, generation, hashCodeNode, equalsNode); StorageEntry entry = mapBuilder.get(null, key, generation, hashCodeNode, equalsNode);
if (entry != null) { if (entry != null) {
return entry.value(); return entry.value();
} else { } else {

View File

@ -1,6 +1,7 @@
package org.enso.interpreter.runtime.data.hash; package org.enso.interpreter.runtime.data.hash;
import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.VirtualFrame;
import java.util.Arrays; import java.util.Arrays;
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode; import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
import org.enso.interpreter.node.expression.builtin.meta.HashCodeNode; import org.enso.interpreter.node.expression.builtin.meta.HashCodeNode;
@ -87,10 +88,10 @@ final class EnsoHashMapBuilder {
* Otherwise it may return new builder suitable for additions. * Otherwise it may return new builder suitable for additions.
*/ */
public EnsoHashMapBuilder asModifiable( public EnsoHashMapBuilder asModifiable(
int atGeneration, HashCodeNode hashCodeNode, EqualsNode equalsNode) { VirtualFrame frame, int atGeneration, HashCodeNode hashCodeNode, EqualsNode equalsNode) {
if (atGeneration != generation || generation * 4 > byHash.length * 3) { if (atGeneration != generation || generation * 4 > byHash.length * 3) {
var newSize = Math.max(actualSize * 2, byHash.length); var newSize = Math.max(actualSize * 2, byHash.length);
return rehash(newSize, atGeneration, hashCodeNode, equalsNode); return rehash(frame, newSize, atGeneration, hashCodeNode, equalsNode);
} else { } else {
return this; return this;
} }
@ -102,7 +103,12 @@ final class EnsoHashMapBuilder {
* equal key, it marks it as removed, if it hasn't been removed yet. Once it finds an empty slot, * equal key, it marks it as removed, if it hasn't been removed yet. Once it finds an empty slot,
* it puts there a new entry with the next generation. * it puts there a new entry with the next generation.
*/ */
public void put(Object key, Object value, HashCodeNode hashCodeNode, EqualsNode equalsNode) { public void put(
VirtualFrame frame,
Object key,
Object value,
HashCodeNode hashCodeNode,
EqualsNode equalsNode) {
var at = findWhereToStart(key, hashCodeNode); var at = findWhereToStart(key, hashCodeNode);
var nextGeneration = ++generation; var nextGeneration = ++generation;
var replacingExistingKey = false; var replacingExistingKey = false;
@ -114,7 +120,7 @@ final class EnsoHashMapBuilder {
byHash[at] = new StorageEntry(key, value, nextGeneration); byHash[at] = new StorageEntry(key, value, nextGeneration);
return; return;
} }
if (compare(equalsNode, byHash[at].key(), key)) { if (compare(frame, equalsNode, byHash[at].key(), key)) {
var invalidatedEntry = byHash[at].markRemoved(nextGeneration); var invalidatedEntry = byHash[at].markRemoved(nextGeneration);
if (invalidatedEntry != byHash[at]) { if (invalidatedEntry != byHash[at]) {
byHash[at] = invalidatedEntry; byHash[at] = invalidatedEntry;
@ -133,14 +139,18 @@ final class EnsoHashMapBuilder {
* given {@code generation}. * given {@code generation}.
*/ */
public StorageEntry get( public StorageEntry get(
Object key, int generation, HashCodeNode hashCodeNode, EqualsNode equalsNode) { VirtualFrame frame,
Object key,
int generation,
HashCodeNode hashCodeNode,
EqualsNode equalsNode) {
var at = findWhereToStart(key, hashCodeNode); var at = findWhereToStart(key, hashCodeNode);
for (var i = 0; i < byHash.length; i++) { for (var i = 0; i < byHash.length; i++) {
if (byHash[at] == null) { if (byHash[at] == null) {
return null; return null;
} }
if (byHash[at].isVisible(generation)) { if (byHash[at].isVisible(generation)) {
if (compare(equalsNode, key, byHash[at].key())) { if (compare(frame, equalsNode, key, byHash[at].key())) {
return byHash[at]; return byHash[at];
} }
} }
@ -164,14 +174,15 @@ final class EnsoHashMapBuilder {
* *
* @return true if the removal was successful false otherwise. * @return true if the removal was successful false otherwise.
*/ */
public boolean remove(Object key, HashCodeNode hashCodeNode, EqualsNode equalsNode) { public boolean remove(
VirtualFrame frame, Object key, HashCodeNode hashCodeNode, EqualsNode equalsNode) {
var at = findWhereToStart(key, hashCodeNode); var at = findWhereToStart(key, hashCodeNode);
var nextGeneration = ++generation; var nextGeneration = ++generation;
for (var i = 0; i < byHash.length; i++) { for (var i = 0; i < byHash.length; i++) {
if (byHash[at] == null) { if (byHash[at] == null) {
return false; return false;
} }
if (compare(equalsNode, key, byHash[at].key())) { if (compare(frame, equalsNode, key, byHash[at].key())) {
var invalidatedEntry = byHash[at].markRemoved(nextGeneration); var invalidatedEntry = byHash[at].markRemoved(nextGeneration);
if (invalidatedEntry != byHash[at]) { if (invalidatedEntry != byHash[at]) {
byHash[at] = invalidatedEntry; byHash[at] = invalidatedEntry;
@ -191,12 +202,16 @@ final class EnsoHashMapBuilder {
* atGeneration}. * atGeneration}.
*/ */
private EnsoHashMapBuilder rehash( private EnsoHashMapBuilder rehash(
int size, int atGeneration, HashCodeNode hashCodeNode, EqualsNode equalsNode) { VirtualFrame frame,
int size,
int atGeneration,
HashCodeNode hashCodeNode,
EqualsNode equalsNode) {
var newBuilder = new EnsoHashMapBuilder(size); var newBuilder = new EnsoHashMapBuilder(size);
for (var i = 0; i < byHash.length; i++) { for (var i = 0; i < byHash.length; i++) {
var entry = byHash[i]; var entry = byHash[i];
if (entry != null && entry.isVisible(atGeneration)) { if (entry != null && entry.isVisible(atGeneration)) {
newBuilder.put(entry.key(), entry.value(), hashCodeNode, equalsNode); newBuilder.put(frame, entry.key(), entry.value(), hashCodeNode, equalsNode);
} }
} }
return newBuilder; return newBuilder;
@ -224,11 +239,11 @@ final class EnsoHashMapBuilder {
+ "}"; + "}";
} }
private static boolean compare(EqualsNode equalsNode, Object a, Object b) { private static boolean compare(VirtualFrame frame, EqualsNode equalsNode, Object a, Object b) {
if (a instanceof Double aDbl && b instanceof Double bDbl && aDbl.isNaN() && bDbl.isNaN()) { if (a instanceof Double aDbl && b instanceof Double bDbl && aDbl.isNaN() && bDbl.isNaN()) {
return true; return true;
} else { } else {
return equalsNode.execute(a, b); return equalsNode.execute(frame, a, b);
} }
} }

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;
import com.oracle.truffle.api.dsl.Cached.Shared; import com.oracle.truffle.api.dsl.Cached.Shared;
import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException; import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.StopIterationException; import com.oracle.truffle.api.interop.StopIterationException;
@ -30,17 +31,18 @@ public abstract class HashMapInsertNode extends Node {
return HashMapInsertNodeGen.create(); return HashMapInsertNodeGen.create();
} }
public abstract EnsoHashMap execute(Object self, Object key, Object value); public abstract EnsoHashMap execute(VirtualFrame frame, Object self, Object key, Object value);
@Specialization @Specialization
EnsoHashMap doEnsoHashMap( EnsoHashMap doEnsoHashMap(
VirtualFrame frame,
EnsoHashMap hashMap, EnsoHashMap hashMap,
Object key, Object key,
Object value, Object value,
@Shared("hash") @Cached HashCodeNode hashCodeNode, @Shared("hash") @Cached HashCodeNode hashCodeNode,
@Shared("equals") @Cached EqualsNode equalsNode) { @Shared("equals") @Cached EqualsNode equalsNode) {
var mapBuilder = hashMap.getMapBuilder(false, hashCodeNode, equalsNode); var mapBuilder = hashMap.getMapBuilder(frame, false, hashCodeNode, equalsNode);
mapBuilder.put(key, value, hashCodeNode, equalsNode); mapBuilder.put(frame, key, value, hashCodeNode, equalsNode);
var newMap = mapBuilder.build(); var newMap = mapBuilder.build();
return newMap; return newMap;
} }
@ -51,6 +53,7 @@ public abstract class HashMapInsertNode extends Node {
*/ */
@Specialization(guards = "mapInterop.hasHashEntries(foreignMap)", limit = "3") @Specialization(guards = "mapInterop.hasHashEntries(foreignMap)", limit = "3")
EnsoHashMap doForeign( EnsoHashMap doForeign(
VirtualFrame frame,
Object foreignMap, Object foreignMap,
Object keyToInsert, Object keyToInsert,
Object valueToInsert, Object valueToInsert,
@ -65,8 +68,9 @@ public abstract class HashMapInsertNode extends Node {
Object keyValueArr = iteratorInterop.getIteratorNextElement(entriesIterator); Object keyValueArr = iteratorInterop.getIteratorNextElement(entriesIterator);
Object key = iteratorInterop.readArrayElement(keyValueArr, 0); Object key = iteratorInterop.readArrayElement(keyValueArr, 0);
Object value = iteratorInterop.readArrayElement(keyValueArr, 1); Object value = iteratorInterop.readArrayElement(keyValueArr, 1);
mapBuilder = mapBuilder.asModifiable(mapBuilder.generation(), hashCodeNode, equalsNode); mapBuilder =
mapBuilder.put(key, value, hashCodeNode, equalsNode); mapBuilder.asModifiable(frame, mapBuilder.generation(), hashCodeNode, equalsNode);
mapBuilder.put(frame, key, value, hashCodeNode, equalsNode);
} }
} catch (UnsupportedMessageException | StopIterationException | InvalidArrayIndexException e) { } catch (UnsupportedMessageException | StopIterationException | InvalidArrayIndexException e) {
CompilerDirectives.transferToInterpreter(); CompilerDirectives.transferToInterpreter();
@ -76,8 +80,8 @@ public abstract class HashMapInsertNode extends Node {
+ " has wrongly specified Interop API (hash entries iterator)"; + " has wrongly specified Interop API (hash entries iterator)";
throw new PanicException(Text.create(msg), this); throw new PanicException(Text.create(msg), this);
} }
mapBuilder = mapBuilder.asModifiable(mapBuilder.generation(), hashCodeNode, equalsNode); mapBuilder = mapBuilder.asModifiable(frame, mapBuilder.generation(), hashCodeNode, equalsNode);
mapBuilder.put(keyToInsert, valueToInsert, hashCodeNode, equalsNode); mapBuilder.put(frame, keyToInsert, valueToInsert, hashCodeNode, equalsNode);
return EnsoHashMap.createWithBuilder(mapBuilder); return EnsoHashMap.createWithBuilder(mapBuilder);
} }
} }

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.Cached.Shared;
import com.oracle.truffle.api.dsl.GenerateUncached; import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException; import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.StopIterationException; import com.oracle.truffle.api.interop.StopIterationException;
@ -29,16 +30,17 @@ public abstract class HashMapRemoveNode extends Node {
return HashMapRemoveNodeGen.create(); return HashMapRemoveNodeGen.create();
} }
public abstract EnsoHashMap execute(Object self, Object key); public abstract EnsoHashMap execute(VirtualFrame frame, Object self, Object key);
@Specialization @Specialization
EnsoHashMap removeFromEnsoMap( EnsoHashMap removeFromEnsoMap(
VirtualFrame frame,
EnsoHashMap ensoMap, EnsoHashMap ensoMap,
Object key, Object key,
@Shared("hash") @Cached HashCodeNode hashCodeNode, @Shared("hash") @Cached HashCodeNode hashCodeNode,
@Shared("equals") @Cached EqualsNode equalsNode) { @Shared("equals") @Cached EqualsNode equalsNode) {
var mapBuilder = ensoMap.getMapBuilder(false, hashCodeNode, equalsNode); var mapBuilder = ensoMap.getMapBuilder(frame, false, hashCodeNode, equalsNode);
if (mapBuilder.remove(key, hashCodeNode, equalsNode)) { if (mapBuilder.remove(frame, key, hashCodeNode, equalsNode)) {
return mapBuilder.build(); return mapBuilder.build();
} else { } else {
throw DataflowError.withoutTrace("No such key", null); throw DataflowError.withoutTrace("No such key", null);
@ -47,6 +49,7 @@ public abstract class HashMapRemoveNode extends Node {
@Specialization(guards = "interop.hasHashEntries(map)") @Specialization(guards = "interop.hasHashEntries(map)")
EnsoHashMap removeFromInteropMap( EnsoHashMap removeFromInteropMap(
VirtualFrame frame,
Object map, Object map,
Object keyToRemove, Object keyToRemove,
@CachedLibrary(limit = "5") InteropLibrary interop, @CachedLibrary(limit = "5") InteropLibrary interop,
@ -62,7 +65,7 @@ public abstract class HashMapRemoveNode extends Node {
while (interop.hasIteratorNextElement(entriesIterator)) { while (interop.hasIteratorNextElement(entriesIterator)) {
Object keyValueArr = interop.getIteratorNextElement(entriesIterator); Object keyValueArr = interop.getIteratorNextElement(entriesIterator);
Object key = interop.readArrayElement(keyValueArr, 0); Object key = interop.readArrayElement(keyValueArr, 0);
if ((boolean) equalsNode.execute(keyToRemove, key)) { if (equalsNode.execute(frame, keyToRemove, key)) {
if (keyToRemoveFound) { if (keyToRemoveFound) {
CompilerDirectives.transferToInterpreter(); CompilerDirectives.transferToInterpreter();
var ctx = EnsoContext.get(this); var ctx = EnsoContext.get(this);
@ -72,8 +75,9 @@ public abstract class HashMapRemoveNode extends Node {
} }
} else { } else {
Object value = interop.readArrayElement(keyValueArr, 1); Object value = interop.readArrayElement(keyValueArr, 1);
mapBuilder = mapBuilder.asModifiable(mapBuilder.generation(), hashCodeNode, equalsNode); mapBuilder =
mapBuilder.put(key, value, hashCodeNode, equalsNode); mapBuilder.asModifiable(frame, mapBuilder.generation(), hashCodeNode, equalsNode);
mapBuilder.put(frame, key, value, hashCodeNode, equalsNode);
} }
} }
} catch (UnsupportedMessageException | StopIterationException | InvalidArrayIndexException e) { } catch (UnsupportedMessageException | StopIterationException | InvalidArrayIndexException e) {

View File

@ -231,7 +231,7 @@ public final class Warning implements EnsoObject {
public Warning reassign(Node location) { public Warning reassign(Node location) {
RootNode root = location.getRootNode(); RootNode root = location.getRootNode();
SourceSection section = location.getEncapsulatingSourceSection(); SourceSection section = location.getEncapsulatingSourceSection();
Reassignment reassignment = new Reassignment(root.getName(), section); Reassignment reassignment = new Reassignment(root == null ? "" : root.getName(), section);
return new Warning(value, origin, sequenceId, reassignments.prepend(reassignment)); return new Warning(value, origin, sequenceId, reassignments.prepend(reassignment));
} }

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.Less
> self (that:Complex) = Complex_Comparator.compare self that == Ordering.Greater > self (that:Complex) = Complex_Comparator.compare self that == Ordering.Greater
== self (that:Complex) = Complex_Comparator.compare self that == Ordering.Equal
pending_equality -> Text = "== with conversions isn't yet supported"
Complex.from (that:Number) = Complex.new that Complex.from (that:Number) = Complex.new that
type Complex_Comparator type Complex_Comparator
compare x:Complex y:Complex = if x.re==y.re && x.im==y.im then Ordering.Equal else compare x:Complex y:Complex = if x.re==y.re && x.im==y.im then Ordering.Equal else
if x.im==0 && y.im==0 then Ordering.compare x.re y.re else if x.im==0 && y.im==0 then Ordering.compare x.re y.re else
Nothing Nothing
hash x:Complex = 7*x.re + 11*x.im hash x:Complex = if x.im == 0 then Default_Comparator.hash x.re else
7*x.re + 11*x.im
Comparable.from (_:Complex) = Complex_Comparator Comparable.from (_:Complex) = Complex_Comparator
Comparable.from (_:Number) = Complex_Comparator
add_specs suite_builder = add_specs suite_builder =
@ -591,21 +589,25 @@ add_specs suite_builder =
v2 = (Complex.new 6 6) v2 = (Complex.new 6 6)
Ordering.compare v1 v2 . catch Incomparable_Values (_->42) . should_equal 42 Ordering.compare v1 v2 . catch Incomparable_Values (_->42) . should_equal 42
group_builder.specify "Equality of complex and number" pending=Complex.pending_equality <| group_builder.specify "Equality of complex and number" <|
v1 = (Complex.new 3) v1 = (Complex.new 3)
v2 = 3 v2 = 3
r1 = v1==v2
r2 = v2==v1
r1 . should_be_true
r2 . should_be_true
v1 . should_equal v2 v1 . should_equal v2
v2 . should_equal v1 v2 . should_equal v1
v1==v2 . should_be_true
v2==v1 . should_be_true
group_builder.specify "Equality of number and complex" pending=Complex.pending_equality <| group_builder.specify "Equality of number and complex" <|
v1 = 3 v1 = 3
v2 = (Complex.new 3) v2 = (Complex.new 3)
r1 = v1==v2
r2 = v2==v1
r1 . should_be_true
r2 . should_be_true
v1 . should_equal v2 v1 . should_equal v2
v2 . should_equal v1 v2 . should_equal v1
v1==v2 . should_be_true
v2==v1 . should_be_true
group_builder.specify "Greater or equal of complex and complex" <| group_builder.specify "Greater or equal of complex and complex" <|
v1 = (Complex.new 3) v1 = (Complex.new 3)

View File

@ -235,6 +235,8 @@ add_specs suite_builder =
# no conversions needed when calling `exchange` methods # no conversions needed when calling `exchange` methods
nine . should_equal one nine . should_equal one
suite_builder.group "MultiValue Conversions" group_builder->
group_builder.specify "Requesting Text & Foo" <| group_builder.specify "Requesting Text & Foo" <|
check a (n : Text & Foo) = case a of check a (n : Text & Foo) = case a of
0 -> n.foo 0 -> n.foo