Splitting Gigantic EqualsNode into few Smaller Ones (#6280)

Splitting gigantic `EqualsNode` into few smaller ones
This commit is contained in:
Jaroslav Tulach 2023-04-17 11:29:54 +02:00 committed by GitHub
parent b020e7f97b
commit 413661b366
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 828 additions and 858 deletions

View File

@ -108,22 +108,7 @@ type Any
a = 7 * 21
a == 147
== : Any -> Boolean
== self that =
# If there is No_Such_Conversion, then `self` and `that` are probably
# host or polyglot values, so we just compare them with the default comparator.
eq_self = Comparable.from self
eq_that = Comparable.from that
similar_type = Meta.is_same_object eq_self eq_that
case similar_type of
False -> False
True ->
case Meta.is_same_object eq_self Default_Comparator of
# Shortcut for objects with Default_Comparator, because of the performance.
True -> Comparable.equals_builtin self that
False ->
case eq_self.compare self that of
Ordering.Equal -> True
_ -> False
== self that = @Builtin_Method "Any.=="
## ALIAS Inequality

View File

@ -124,14 +124,6 @@ type Comparable
ordering = Ordering.compare atom that
if ordering.is_error then Nothing else ordering.to_sign
## PRIVATE
A custom comparator is any comparator that is different than the
default ones.
has_custom_comparator : Atom -> Boolean
has_custom_comparator atom =
comp = Comparable.from atom
(comp.is_a Default_Comparator).not
## PRIVATE
Default implementation of a _comparator_.
@Builtin_Type
@ -139,7 +131,7 @@ type Default_Comparator
## PRIVATE
compare : Any -> Any -> (Ordering|Nothing)
compare x y =
case Comparable.equals_builtin x y of
case Any.== x y of
True -> Ordering.Equal
False ->
case Comparable.less_than_builtin x y of

View File

@ -44,8 +44,6 @@ public class CurriedFunctionBenchmarks {
var benchmarkName = params.getBenchmark().replaceFirst(".*\\.", "");
var code = """
import Standard.Base.Any.Any
avg fn len =
sum acc i = if i == len then acc else
sum (acc + fn i) i+1

View File

@ -71,7 +71,6 @@ public class EqualsBenchmarks {
var benchmarkName = params.getBenchmark().replaceFirst(".*\\.", "");
var codeBuilder = new StringBuilder("""
import Standard.Base.Data.Range.Extensions
import Standard.Base.Any.Any
type Node
C1 f1

View File

@ -48,7 +48,6 @@ public class VectorBenchmarks {
var code = """
import Standard.Base.Data.Vector.Vector
import Standard.Base.Data.Array_Proxy.Array_Proxy
import Standard.Base.Any.Any
avg arr =
sum acc i = if i == arr.length then acc else

View File

@ -7,7 +7,6 @@ class AtomFixtures extends DefaultInterpreterRunner {
val millionElementList = eval(
s"""import Standard.Base.Data.List.List
|import Standard.Base.Any.Any
|
|main =
| generator fn acc i end = if i == end then acc else @Tail_Call generator fn (fn acc i) i+1 end
@ -17,7 +16,6 @@ class AtomFixtures extends DefaultInterpreterRunner {
val generateListCode =
"""import Standard.Base.Data.List.List
|import Standard.Base.Any.Any
|
|main = length ->
| generator = acc -> i -> if i == 0 then acc else @Tail_Call generator (List.Cons i acc) (i - 1)
@ -29,7 +27,6 @@ class AtomFixtures extends DefaultInterpreterRunner {
val generateListQualifiedCode =
"""import Standard.Base.Data.List.List
|import Standard.Base.Any.Any
|
|main = length ->
| generator = acc -> i -> if i == 0 then acc else @Tail_Call generator (List.Cons i acc) (i - 1)

View File

@ -8,7 +8,6 @@ class CallableFixtures extends DefaultInterpreterRunner {
val sumTCOfromCallCode =
"""
|from Standard.Base.Data.Numbers import all
|import Standard.Base.Any.Any
|
|type Foo
|
@ -23,8 +22,7 @@ class CallableFixtures extends DefaultInterpreterRunner {
val sumTCOmethodCallCode =
"""import Standard.Base.Any.Any
|
"""
|summator = acc -> current ->
| if current == 0 then acc else @Tail_Call summator (acc + current) (current - 1)
|
@ -35,8 +33,7 @@ class CallableFixtures extends DefaultInterpreterRunner {
val sumTCOmethodCall = getMain(sumTCOmethodCallCode)
val sumTCOmethodCallWithNamedArgumentsCode =
"""import Standard.Base.Any.Any
|
"""
|summator = acc -> current ->
| if current == 0 then acc else @Tail_Call summator (current = current - 1) (acc = acc + current)
|
@ -48,8 +45,7 @@ class CallableFixtures extends DefaultInterpreterRunner {
getMain(sumTCOmethodCallWithNamedArgumentsCode)
val sumTCOmethodCallWithDefaultedArgumentsCode =
"""import Standard.Base.Any.Any
|
"""
|summator = (acc = 0) -> current ->
| if current == 0 then acc else @Tail_Call summator (current = current - 1) (acc = acc + current)
|

View File

@ -6,8 +6,7 @@ class NamedDefaultedArgumentFixtures extends DefaultInterpreterRunner {
val hundredMillion: Long = 100000000
val sumTCOWithNamedArgumentsCode =
"""import Standard.Base.Any.Any
|
"""
|main = sumTo ->
| summator = acc -> current ->
| if current == 0 then acc else @Tail_Call summator (current = current - 1) (acc = acc + current)
@ -18,8 +17,7 @@ class NamedDefaultedArgumentFixtures extends DefaultInterpreterRunner {
val sumTCOWithNamedArguments = getMain(sumTCOWithNamedArgumentsCode)
val sumTCOWithDefaultedArgumentsCode =
"""import Standard.Base.Any.Any
|
"""
|main = sumTo ->
| summator = (acc = 0) -> current ->
| if current == 0 then acc else @Tail_Call summator (current = current - 1) (acc = acc + current)

View File

@ -9,8 +9,7 @@ class RecursionFixtures extends DefaultInterpreterRunner {
val hundred: Long = 100
val sumTCOCode =
"""import Standard.Base.Any.Any
|
"""
|main = sumTo ->
| summator = acc -> current ->
| if current == 0 then acc else @Tail_Call summator acc+current current-1
@ -21,8 +20,7 @@ class RecursionFixtures extends DefaultInterpreterRunner {
val sumTCO = getMain(sumTCOCode)
val sumTCOFoldLikeCode =
"""import Standard.Base.Any.Any
|
"""
|main = sumTo ->
| summator = acc -> i -> f ->
| if i == 0 then acc else @Tail_Call summator (f acc i) (i - 1) f
@ -32,8 +30,7 @@ class RecursionFixtures extends DefaultInterpreterRunner {
val sumTCOFoldLike = getMain(sumTCOFoldLikeCode)
val sumRecursiveCode =
"""import Standard.Base.Any.Any
|
"""
|main = sumTo ->
| summator = i -> if i == 0 then 0 else i + summator (i - 1)
| res = summator sumTo
@ -42,8 +39,7 @@ class RecursionFixtures extends DefaultInterpreterRunner {
val sumRecursive = getMain(sumRecursiveCode)
val oversaturatedRecursiveCallTCOCode =
"""import Standard.Base.Any.Any
|
"""
|main = sumTo ->
| summator = acc -> i -> f ->
| if i == 0 then acc else @Tail_Call summator (f acc i) (i - 1) f

View File

@ -0,0 +1,141 @@
package org.enso.interpreter.node.expression.builtin.meta;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.ConditionProfile;
import java.util.Arrays;
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.node.expression.builtin.ordering.CustomComparatorNode;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.callable.atom.StructsLibrary;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.Type;
import org.enso.interpreter.runtime.state.State;
@GenerateUncached
public abstract class EqualsAtomNode extends Node {
public static EqualsAtomNode build() {
return EqualsAtomNodeGen.create();
}
public abstract boolean execute(Atom left, Atom right);
static EqualsNode[] createEqualsNodes(int size) {
EqualsNode[] nodes = new EqualsNode[size];
Arrays.fill(nodes, EqualsNode.build());
return nodes;
}
@Specialization(
guards = {
"selfCtorCached == self.getConstructor()",
"customComparatorNode.execute(self) == null"
},
limit = "10")
@ExplodeLoop
boolean equalsAtoms(
Atom self,
Atom other,
@Cached("self.getConstructor()") AtomConstructor selfCtorCached,
@Cached(value = "selfCtorCached.getFields().length", allowUncached = true)
int fieldsLenCached,
@Cached(value = "createEqualsNodes(fieldsLenCached)", allowUncached = true)
EqualsNode[] fieldEqualsNodes,
@Cached CustomComparatorNode customComparatorNode,
@Cached ConditionProfile constructorsNotEqualProfile,
@CachedLibrary(limit = "5") StructsLibrary structsLib) {
if (constructorsNotEqualProfile.profile(self.getConstructor() != other.getConstructor())) {
return false;
}
var selfFields = structsLib.getFields(self);
var otherFields = structsLib.getFields(other);
assert selfFields.length == otherFields.length
: "Constructors are same, atoms should have the same number of fields";
CompilerAsserts.partialEvaluationConstant(fieldsLenCached);
for (int i = 0; i < fieldsLenCached; i++) {
boolean fieldsAreEqual = fieldEqualsNodes[i].execute(selfFields[i], otherFields[i]);
if (!fieldsAreEqual) {
return false;
}
}
return true;
}
@Specialization(
guards = {
"selfCtorCached == self.getConstructor()",
"cachedComparator != null",
},
limit = "10")
boolean equalsAtoms(
Atom self,
Atom other,
@Cached("self.getConstructor()") AtomConstructor selfCtorCached,
@Cached CustomComparatorNode customComparatorNode,
@Cached(value = "customComparatorNode.execute(self)") Type cachedComparator,
@Cached(value = "findCompareMethod(cachedComparator)", allowUncached = true)
Function compareFn,
@Cached(value = "invokeCompareNode(compareFn)") InvokeFunctionNode invokeNode) {
var otherComparator = customComparatorNode.execute(other);
if (cachedComparator != otherComparator) {
return false;
}
var ctx = EnsoContext.get(this);
var args = new Object[] {cachedComparator, self, other};
var result = invokeNode.execute(compareFn, null, State.create(ctx), args);
return ctx.getBuiltins().ordering().newEqual() == result;
}
@CompilerDirectives.TruffleBoundary
@Specialization(replaces = "equalsAtoms")
boolean equalsAtomsUncached(Atom self, Atom other) {
if (self.getConstructor() != other.getConstructor()) {
return false;
}
Object[] selfFields = StructsLibrary.getUncached().getFields(self);
Object[] otherFields = StructsLibrary.getUncached().getFields(other);
if (selfFields.length != otherFields.length) {
return false;
}
for (int i = 0; i < selfFields.length; i++) {
boolean areFieldsSame = EqualsNodeGen.getUncached().execute(selfFields[i], otherFields[i]);
if (!areFieldsSame) {
return false;
}
}
return true;
}
@TruffleBoundary
static Function findCompareMethod(Type comparator) {
var fn = comparator.getDefinitionScope().getMethods().get(comparator).get("compare");
if (fn == null) {
throw new AssertionError("No compare function for " + comparator);
}
return fn;
}
static InvokeFunctionNode invokeCompareNode(Function compareFn) {
CallArgumentInfo[] argsInfo = new CallArgumentInfo[compareFn.getSchema().getArgumentsCount()];
for (int i = 0; i < argsInfo.length; i++) {
var argDef = compareFn.getSchema().getArgumentInfos()[i];
argsInfo[i] = new CallArgumentInfo(argDef.getName());
}
return InvokeFunctionNode.build(
argsInfo, DefaultsExecutionMode.EXECUTE, ArgumentsExecutionMode.EXECUTE);
}
}

View File

@ -0,0 +1,565 @@
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.GenerateUncached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.StopIterationException;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnknownKeyException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.Node;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import org.enso.interpreter.dsl.AcceptsError;
import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode;
import org.enso.interpreter.node.expression.builtin.ordering.CustomComparatorNode;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.Module;
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.data.EnsoFile;
import org.enso.interpreter.runtime.data.Type;
import org.enso.interpreter.runtime.error.WarningsLibrary;
import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
import org.enso.interpreter.runtime.scope.ModuleScope;
@GenerateUncached
public abstract class EqualsComplexNode extends Node {
public static EqualsComplexNode build() {
return EqualsComplexNodeGen.create();
}
public abstract boolean execute(@AcceptsError Object left, @AcceptsError Object right);
/** Enso specific types */
@Specialization
boolean equalsUnresolvedSymbols(
UnresolvedSymbol self, UnresolvedSymbol otherSymbol, @Cached EqualsNode equalsNode) {
return self.getName().equals(otherSymbol.getName())
&& equalsNode.execute(self.getScope(), otherSymbol.getScope());
}
@Specialization
boolean equalsUnresolvedConversion(
UnresolvedConversion selfConversion,
UnresolvedConversion otherConversion,
@Cached EqualsNode equalsNode) {
return equalsNode.execute(selfConversion.getScope(), otherConversion.getScope());
}
@Specialization
boolean equalsModuleScopes(
ModuleScope selfModuleScope, ModuleScope otherModuleScope, @Cached EqualsNode equalsNode) {
return equalsNode.execute(selfModuleScope.getModule(), otherModuleScope.getModule());
}
@Specialization
@TruffleBoundary
boolean equalsModules(Module selfModule, Module otherModule, @Cached EqualsNode equalsNode) {
return equalsNode.execute(selfModule.getName().toString(), otherModule.getName().toString());
}
@Specialization
boolean equalsFiles(EnsoFile selfFile, EnsoFile otherFile, @Cached EqualsNode equalsNode) {
return equalsNode.execute(selfFile.getPath(), otherFile.getPath());
}
/**
* There is no specialization for {@link TypesLibrary#hasType(Object)}, because also primitive
* values would fall into that specialization, and it would be too complicated to make that
* specialization disjunctive. So we rather specialize directly for {@link Type types}.
*/
@Specialization(guards = {"typesLib.hasType(selfType)", "typesLib.hasType(otherType)"})
boolean equalsTypes(
Type selfType,
Type otherType,
@Cached EqualsNode equalsNode,
@CachedLibrary(limit = "5") TypesLibrary typesLib) {
return equalsNode.execute(
selfType.getQualifiedName().toString(), otherType.getQualifiedName().toString());
}
/**
* If one of the objects has warnings attached, just treat it as an object without any warnings.
*/
@Specialization(
guards = {
"selfWarnLib.hasWarnings(selfWithWarnings) || otherWarnLib.hasWarnings(otherWithWarnings)"
},
limit = "3")
boolean equalsWithWarnings(
Object selfWithWarnings,
Object otherWithWarnings,
@CachedLibrary("selfWithWarnings") WarningsLibrary selfWarnLib,
@CachedLibrary("otherWithWarnings") WarningsLibrary otherWarnLib,
@Cached EqualsNode equalsNode) {
try {
Object self =
selfWarnLib.hasWarnings(selfWithWarnings)
? selfWarnLib.removeWarnings(selfWithWarnings)
: selfWithWarnings;
Object other =
otherWarnLib.hasWarnings(otherWithWarnings)
? otherWarnLib.removeWarnings(otherWithWarnings)
: otherWithWarnings;
return equalsNode.execute(self, other);
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
/** Interop libraries * */
@Specialization(
guards = {
"selfInterop.isNull(selfNull) || otherInterop.isNull(otherNull)",
},
limit = "3")
boolean equalsNull(
Object selfNull,
Object otherNull,
@CachedLibrary("selfNull") InteropLibrary selfInterop,
@CachedLibrary("otherNull") InteropLibrary otherInterop) {
return selfInterop.isNull(selfNull) && otherInterop.isNull(otherNull);
}
@Specialization(
guards = {"selfInterop.isBoolean(selfBoolean)", "otherInterop.isBoolean(otherBoolean)"},
limit = "3")
boolean equalsBooleanInterop(
Object selfBoolean,
Object otherBoolean,
@CachedLibrary("selfBoolean") InteropLibrary selfInterop,
@CachedLibrary("otherBoolean") InteropLibrary otherInterop) {
try {
return selfInterop.asBoolean(selfBoolean) == otherInterop.asBoolean(otherBoolean);
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
@Specialization(
guards = {
"isTimeZone(selfTimeZone, selfInterop)",
"isTimeZone(otherTimeZone, otherInterop)",
},
limit = "3")
boolean equalsTimeZones(
Object selfTimeZone,
Object otherTimeZone,
@CachedLibrary("selfTimeZone") InteropLibrary selfInterop,
@CachedLibrary("otherTimeZone") InteropLibrary otherInterop) {
try {
return selfInterop.asTimeZone(selfTimeZone).equals(otherInterop.asTimeZone(otherTimeZone));
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
@TruffleBoundary
@Specialization(
guards = {
"isZonedDateTime(selfZonedDateTime, selfInterop)",
"isZonedDateTime(otherZonedDateTime, otherInterop)",
},
limit = "3")
boolean equalsZonedDateTimes(
Object selfZonedDateTime,
Object otherZonedDateTime,
@CachedLibrary("selfZonedDateTime") InteropLibrary selfInterop,
@CachedLibrary("otherZonedDateTime") InteropLibrary otherInterop) {
try {
var self =
ZonedDateTime.of(
selfInterop.asDate(selfZonedDateTime),
selfInterop.asTime(selfZonedDateTime),
selfInterop.asTimeZone(selfZonedDateTime));
var other =
ZonedDateTime.of(
otherInterop.asDate(otherZonedDateTime),
otherInterop.asTime(otherZonedDateTime),
otherInterop.asTimeZone(otherZonedDateTime));
// We cannot use self.isEqual(other), because that does not include timezone.
return self.compareTo(other) == 0;
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
@Specialization(
guards = {
"isDateTime(selfDateTime, selfInterop)",
"isDateTime(otherDateTime, otherInterop)",
},
limit = "3")
boolean equalsDateTimes(
Object selfDateTime,
Object otherDateTime,
@CachedLibrary("selfDateTime") InteropLibrary selfInterop,
@CachedLibrary("otherDateTime") InteropLibrary otherInterop) {
try {
var self =
LocalDateTime.of(selfInterop.asDate(selfDateTime), selfInterop.asTime(selfDateTime));
var other =
LocalDateTime.of(otherInterop.asDate(otherDateTime), otherInterop.asTime(otherDateTime));
return self.isEqual(other);
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
@Specialization(
guards = {
"isDate(selfDate, selfInterop)",
"isDate(otherDate, otherInterop)",
},
limit = "3")
boolean equalsDates(
Object selfDate,
Object otherDate,
@CachedLibrary("selfDate") InteropLibrary selfInterop,
@CachedLibrary("otherDate") InteropLibrary otherInterop) {
try {
return selfInterop.asDate(selfDate).isEqual(otherInterop.asDate(otherDate));
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
@Specialization(
guards = {
"isTime(selfTime, selfInterop)",
"isTime(otherTime, otherInterop)",
},
limit = "3")
boolean equalsTimes(
Object selfTime,
Object otherTime,
@CachedLibrary("selfTime") InteropLibrary selfInterop,
@CachedLibrary("otherTime") InteropLibrary otherInterop) {
try {
return selfInterop.asTime(selfTime).equals(otherInterop.asTime(otherTime));
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
@Specialization(
guards = {"selfInterop.isDuration(selfDuration)", "otherInterop.isDuration(otherDuration)"},
limit = "3")
boolean equalsDuration(
Object selfDuration,
Object otherDuration,
@CachedLibrary("selfDuration") InteropLibrary selfInterop,
@CachedLibrary("otherDuration") InteropLibrary otherInterop) {
try {
return selfInterop.asDuration(selfDuration).equals(otherInterop.asDuration(otherDuration));
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
@Specialization(
guards = {
"selfInterop.hasArrayElements(selfArray)",
"otherInterop.hasArrayElements(otherArray)",
"!selfInterop.hasHashEntries(selfArray)",
"!otherInterop.hasHashEntries(otherArray)",
},
limit = "3")
boolean equalsArrays(
Object selfArray,
Object otherArray,
@CachedLibrary("selfArray") InteropLibrary selfInterop,
@CachedLibrary("otherArray") InteropLibrary otherInterop,
@Cached EqualsNode equalsNode,
@Cached CustomComparatorNode hasCustomComparatorNode,
@Cached HostValueToEnsoNode valueToEnsoNode) {
try {
long selfSize = selfInterop.getArraySize(selfArray);
if (selfSize != otherInterop.getArraySize(otherArray)) {
return false;
}
for (long i = 0; i < selfSize; i++) {
Object selfElem = valueToEnsoNode.execute(selfInterop.readArrayElement(selfArray, i));
Object otherElem = valueToEnsoNode.execute(otherInterop.readArrayElement(otherArray, i));
boolean elemsAreEqual = equalsNode.execute(selfElem, otherElem);
if (!elemsAreEqual) {
return false;
}
}
return true;
} catch (UnsupportedMessageException | InvalidArrayIndexException e) {
throw new IllegalStateException(e);
}
}
@Specialization(
guards = {
"selfInterop.hasHashEntries(selfHashMap)",
"otherInterop.hasHashEntries(otherHashMap)",
"!selfInterop.hasArrayElements(selfHashMap)",
"!otherInterop.hasArrayElements(otherHashMap)"
},
limit = "3")
boolean equalsHashMaps(
Object selfHashMap,
Object otherHashMap,
@CachedLibrary("selfHashMap") InteropLibrary selfInterop,
@CachedLibrary("otherHashMap") InteropLibrary otherInterop,
@CachedLibrary(limit = "5") InteropLibrary entriesInterop,
@Cached EqualsNode equalsNode,
@Cached HostValueToEnsoNode valueToEnsoNode) {
try {
int selfHashSize = (int) selfInterop.getHashSize(selfHashMap);
int otherHashSize = (int) otherInterop.getHashSize(otherHashMap);
if (selfHashSize != otherHashSize) {
return false;
}
Object selfEntriesIter = selfInterop.getHashEntriesIterator(selfHashMap);
while (entriesInterop.hasIteratorNextElement(selfEntriesIter)) {
Object selfKeyValue = entriesInterop.getIteratorNextElement(selfEntriesIter);
Object key = entriesInterop.readArrayElement(selfKeyValue, 0);
Object selfValue =
valueToEnsoNode.execute(entriesInterop.readArrayElement(selfKeyValue, 1));
if (otherInterop.isHashEntryExisting(otherHashMap, key)
&& otherInterop.isHashEntryReadable(otherHashMap, key)) {
Object otherValue =
valueToEnsoNode.execute(otherInterop.readHashValue(otherHashMap, key));
if (!equalsNode.execute(selfValue, otherValue)) {
return false;
}
} else {
return false;
}
}
return true;
} catch (UnsupportedMessageException
| StopIterationException
| UnknownKeyException
| InvalidArrayIndexException e) {
throw new IllegalStateException(e);
}
}
@Specialization(
guards = {
"isObjectWithMembers(selfObject, interop)",
"isObjectWithMembers(otherObject, interop)",
})
boolean equalsInteropObjectWithMembers(
Object selfObject,
Object otherObject,
@CachedLibrary(limit = "10") InteropLibrary interop,
@CachedLibrary(limit = "5") TypesLibrary typesLib,
@Cached EqualsNode equalsNode,
@Cached HostValueToEnsoNode valueToEnsoNode) {
try {
Object selfMembers = interop.getMembers(selfObject);
Object otherMembers = interop.getMembers(otherObject);
assert interop.getArraySize(selfMembers) < Integer.MAX_VALUE
: "Long array sizes not supported";
int membersSize = (int) interop.getArraySize(selfMembers);
if (interop.getArraySize(otherMembers) != membersSize) {
return false;
}
// Check member names
String[] memberNames = new String[membersSize];
for (int i = 0; i < membersSize; i++) {
String selfMemberName = interop.asString(interop.readArrayElement(selfMembers, i));
String otherMemberName = interop.asString(interop.readArrayElement(otherMembers, i));
if (!equalsNode.execute(selfMemberName, otherMemberName)) {
return false;
}
memberNames[i] = selfMemberName;
}
// Check member values
for (int i = 0; i < membersSize; i++) {
if (interop.isMemberReadable(selfObject, memberNames[i])
&& interop.isMemberReadable(otherObject, memberNames[i])) {
Object selfMember =
valueToEnsoNode.execute(interop.readMember(selfObject, memberNames[i]));
Object otherMember =
valueToEnsoNode.execute(interop.readMember(otherObject, memberNames[i]));
if (!equalsNode.execute(selfMember, otherMember)) {
return false;
}
}
}
return true;
} catch (UnsupportedMessageException
| InvalidArrayIndexException
| UnknownIdentifierException e) {
throw new IllegalStateException(
String.format(
"One of the interop objects has probably wrongly specified interop API "
+ "for members. selfObject = %s ; otherObject = %s",
selfObject, otherObject),
e);
}
}
@Specialization(guards = {"isHostObject(selfHostObject)", "isHostObject(otherHostObject)"})
boolean equalsHostObjects(
Object selfHostObject,
Object otherHostObject,
@CachedLibrary(limit = "5") InteropLibrary interop) {
try {
return interop.asBoolean(interop.invokeMember(selfHostObject, "equals", otherHostObject));
} catch (UnsupportedMessageException
| ArityException
| UnknownIdentifierException
| UnsupportedTypeException e) {
throw new IllegalStateException(e);
}
}
// HostFunction is identified by a qualified name, it is not a lambda.
// It has well-defined equality based on the qualified name.
@Specialization(guards = {"isHostFunction(selfHostFunc)", "isHostFunction(otherHostFunc)"})
boolean equalsHostFunctions(
Object selfHostFunc,
Object otherHostFunc,
@CachedLibrary(limit = "5") InteropLibrary interop,
@Cached EqualsNode equalsNode) {
Object selfFuncStrRepr = interop.toDisplayString(selfHostFunc);
Object otherFuncStrRepr = interop.toDisplayString(otherHostFunc);
return equalsNode.execute(selfFuncStrRepr, otherFuncStrRepr);
}
@Specialization(guards = "fallbackGuard(left, right, interop, warningsLib)")
@TruffleBoundary
boolean equalsGeneric(
Object left,
Object right,
@CachedLibrary(limit = "10") InteropLibrary interop,
@CachedLibrary(limit = "10") TypesLibrary typesLib,
@CachedLibrary(limit = "10") WarningsLibrary warningsLib) {
return left == right
|| interop.isIdentical(left, right, interop)
|| left.equals(right)
|| (isNullOrNothing(left, typesLib, interop) && isNullOrNothing(right, typesLib, interop));
}
// We have to manually specify negation of guards of other specializations, because
// we cannot use @Fallback here. Note that this guard is not precisely the negation of
// all the other guards on purpose.
boolean fallbackGuard(
Object left, Object right, InteropLibrary interop, WarningsLibrary warnings) {
if (EqualsNode.isPrimitive(left, interop) && EqualsNode.isPrimitive(right, interop)) {
return false;
}
if (isHostObject(left) && isHostObject(right)) {
return false;
}
if (isHostFunction(left) && isHostFunction(right)) {
return false;
}
if (left instanceof Atom && right instanceof Atom) {
return false;
}
if (interop.isNull(left) && interop.isNull(right)) {
return false;
}
if (interop.isString(left) && interop.isString(right)) {
return false;
}
if (interop.hasArrayElements(left) && interop.hasArrayElements(right)) {
return false;
}
if (interop.hasHashEntries(left) && interop.hasHashEntries(right)) {
return false;
}
if (isObjectWithMembers(left, interop) && isObjectWithMembers(right, interop)) {
return false;
}
if (isTimeZone(left, interop) && isTimeZone(right, interop)) {
return false;
}
if (isZonedDateTime(left, interop) && isZonedDateTime(right, interop)) {
return false;
}
if (isDateTime(left, interop) && isDateTime(right, interop)) {
return false;
}
if (isDate(left, interop) && isDate(right, interop)) {
return false;
}
if (isTime(left, interop) && isTime(right, interop)) {
return false;
}
if (interop.isDuration(left) && interop.isDuration(right)) {
return false;
}
if (warnings.hasWarnings(left) || warnings.hasWarnings(right)) {
return false;
}
// For all other cases, fall through to the generic specialization
return true;
}
boolean isTimeZone(Object object, InteropLibrary interop) {
return !interop.isTime(object) && !interop.isDate(object) && interop.isTimeZone(object);
}
boolean isZonedDateTime(Object object, InteropLibrary interop) {
return interop.isTime(object) && interop.isDate(object) && interop.isTimeZone(object);
}
boolean isDateTime(Object object, InteropLibrary interop) {
return interop.isTime(object) && interop.isDate(object) && !interop.isTimeZone(object);
}
boolean isDate(Object object, InteropLibrary interop) {
return !interop.isTime(object) && interop.isDate(object) && !interop.isTimeZone(object);
}
boolean isTime(Object object, InteropLibrary interop) {
return interop.isTime(object) && !interop.isDate(object) && !interop.isTimeZone(object);
}
boolean isObjectWithMembers(Object object, InteropLibrary interop) {
if (object instanceof Atom) {
return false;
}
if (isHostObject(object)) {
return false;
}
if (interop.isDate(object)) {
return false;
}
if (interop.isTime(object)) {
return false;
}
return interop.hasMembers(object);
}
private boolean isNullOrNothing(Object object, TypesLibrary typesLib, InteropLibrary interop) {
if (typesLib.hasType(object)) {
return typesLib.getType(object) == EnsoContext.get(this).getNothing();
} else if (interop.isNull(object)) {
return true;
} else {
return object == null;
}
}
@TruffleBoundary
boolean isHostObject(Object object) {
return EnsoContext.get(this).getEnvironment().isHostObject(object);
}
@TruffleBoundary
boolean isHostFunction(Object object) {
return EnsoContext.get(this).getEnvironment().isHostFunction(object);
}
}

View File

@ -1,64 +1,36 @@
package org.enso.interpreter.node.expression.builtin.meta;
import com.ibm.icu.text.Normalizer;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.StopIterationException;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnknownKeyException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.ConditionProfile;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.Arrays;
import org.enso.interpreter.dsl.AcceptsError;
import org.enso.interpreter.dsl.BuiltinMethod;
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.node.expression.builtin.number.utils.BigIntegerOps;
import org.enso.interpreter.node.expression.builtin.ordering.HasCustomComparatorNode;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.Module;
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
import org.enso.interpreter.runtime.callable.UnresolvedSymbol;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.callable.atom.StructsLibrary;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.EnsoFile;
import org.enso.interpreter.runtime.data.Type;
import org.enso.interpreter.runtime.data.text.Text;
import org.enso.interpreter.runtime.error.WarningsLibrary;
import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
import org.enso.interpreter.runtime.number.EnsoBigInteger;
import org.enso.interpreter.runtime.scope.ModuleScope;
import org.enso.interpreter.runtime.state.State;
@BuiltinMethod(
type = "Comparable",
name = "equals_builtin",
type = "Any",
name = "==",
description = """
Compares self with other object and returns True iff `self` is exactly the same as
the other object, including all its transitively accessible properties or fields,
False otherwise.
Can handle arbitrary objects, including all foreign objects.
Does not throw dataflow errors or panics.
Note that this is different than `Meta.is_same_object`, which checks whether two
references point to the same object on the heap.
"""
@ -70,7 +42,7 @@ public abstract class EqualsNode extends Node {
return EqualsNodeGen.create();
}
public abstract boolean execute(@AcceptsError Object left, @AcceptsError Object right);
public abstract boolean execute(@AcceptsError Object self, @AcceptsError Object right);
/**
* Primitive values
@ -102,11 +74,6 @@ public abstract class EqualsNode extends Node {
return false;
}
@Specialization
boolean equalsByteByte(byte self, byte other) {
return self == other;
}
@Specialization
boolean equalsLongLong(long self, long other) {
return self == other;
@ -117,11 +84,6 @@ public abstract class EqualsNode extends Node {
return false;
}
@Specialization
boolean equalsLongInt(long self, int other) {
return self == (long) other;
}
@Specialization
boolean equalsLongDouble(long self, double other) {
return (double) self == other;
@ -151,11 +113,6 @@ public abstract class EqualsNode extends Node {
return false;
}
@Specialization
boolean equalsDoubleInt(double self, int other) {
return self == (double) other;
}
@Specialization
@TruffleBoundary
boolean equalsDoubleBigInt(double self, EnsoBigInteger other) {
@ -167,21 +124,6 @@ public abstract class EqualsNode extends Node {
return false;
}
@Specialization
boolean equalsIntInt(int self, int other) {
return self == other;
}
@Specialization
boolean equalsIntLong(int self, long other) {
return (long) self == other;
}
@Specialization
boolean equalsIntDouble(int self, double other) {
return (double) self == other;
}
@Specialization
@TruffleBoundary
boolean equalsBigIntBigInt(EnsoBigInteger self, EnsoBigInteger otherBigInt) {
@ -224,15 +166,12 @@ public abstract class EqualsNode extends Node {
}
}
@Specialization(limit = "3")
boolean equalsTextText(Text selfText, Text otherText,
@CachedLibrary("selfText") InteropLibrary selfInterop,
@CachedLibrary("otherText") InteropLibrary otherInterop) {
if (selfText.is_normalized() && otherText.is_normalized()) {
return selfText.toString().compareTo(otherText.toString()) == 0;
} else {
return equalsStrings(selfText, otherText, selfInterop, otherInterop);
}
@Specialization(guards = {
"selfText.is_normalized()",
"otherText.is_normalized()"
})
boolean equalsTextText(Text selfText, Text otherText) {
return selfText.toString().compareTo(otherText.toString()) == 0;
}
@Specialization
@ -255,228 +194,6 @@ public abstract class EqualsNode extends Node {
return false;
}
/**
* Enso specific types
**/
@Specialization
boolean equalsUnresolvedSymbols(UnresolvedSymbol self, UnresolvedSymbol otherSymbol,
@Cached EqualsNode equalsNode) {
return self.getName().equals(otherSymbol.getName())
&& equalsNode.execute(self.getScope(), otherSymbol.getScope());
}
@Specialization
boolean equalsUnresolvedConversion(UnresolvedConversion selfConversion, UnresolvedConversion otherConversion,
@Cached EqualsNode equalsNode) {
return equalsNode.execute(selfConversion.getScope(), otherConversion.getScope());
}
@Specialization
boolean equalsModuleScopes(ModuleScope selfModuleScope, ModuleScope otherModuleScope,
@Cached EqualsNode equalsNode) {
return equalsNode.execute(selfModuleScope.getModule(), otherModuleScope.getModule());
}
@Specialization
@TruffleBoundary
boolean equalsModules(Module selfModule, Module otherModule,
@Cached EqualsNode equalsNode) {
return equalsNode.execute(selfModule.getName().toString(), otherModule.getName().toString());
}
@Specialization
boolean equalsFiles(EnsoFile selfFile, EnsoFile otherFile,
@CachedLibrary(limit = "5") InteropLibrary interop) {
return equalsStrings(selfFile.getPath(), otherFile.getPath(), interop, interop);
}
/**
* There is no specialization for {@link TypesLibrary#hasType(Object)}, because also
* primitive values would fall into that specialization, and it would be too complicated
* to make that specialization disjunctive. So we rather specialize directly for
* {@link Type types}.
*/
@Specialization(guards = {
"typesLib.hasType(selfType)",
"typesLib.hasType(otherType)"
})
boolean equalsTypes(Type selfType, Type otherType,
@Cached EqualsNode equalsNode,
@CachedLibrary(limit = "5") TypesLibrary typesLib) {
return equalsNode.execute(
selfType.getQualifiedName().toString(),
otherType.getQualifiedName().toString()
);
}
/**
* If one of the objects has warnings attached, just treat it as an object without any
* warnings.
*/
@Specialization(guards = {
"selfWarnLib.hasWarnings(selfWithWarnings) || otherWarnLib.hasWarnings(otherWithWarnings)"
}, limit = "3")
boolean equalsWithWarnings(Object selfWithWarnings, Object otherWithWarnings,
@CachedLibrary("selfWithWarnings") WarningsLibrary selfWarnLib,
@CachedLibrary("otherWithWarnings") WarningsLibrary otherWarnLib,
@Cached EqualsNode equalsNode
) {
try {
Object self =
selfWarnLib.hasWarnings(selfWithWarnings) ? selfWarnLib.removeWarnings(selfWithWarnings)
: selfWithWarnings;
Object other =
otherWarnLib.hasWarnings(otherWithWarnings) ? otherWarnLib.removeWarnings(otherWithWarnings)
: otherWithWarnings;
return equalsNode.execute(self, other);
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
/** Interop libraries **/
@Specialization(guards = {
"selfInterop.isNull(selfNull) || otherInterop.isNull(otherNull)",
}, limit = "3")
boolean equalsNull(
Object selfNull, Object otherNull,
@CachedLibrary("selfNull") InteropLibrary selfInterop,
@CachedLibrary("otherNull") InteropLibrary otherInterop
) {
return selfInterop.isNull(selfNull) && otherInterop.isNull(otherNull);
}
@Specialization(guards = {
"selfInterop.isBoolean(selfBoolean)",
"otherInterop.isBoolean(otherBoolean)"
}, limit = "3")
boolean equalsBooleanInterop(
Object selfBoolean,
Object otherBoolean,
@CachedLibrary("selfBoolean") InteropLibrary selfInterop,
@CachedLibrary("otherBoolean") InteropLibrary otherInterop
) {
try {
return selfInterop.asBoolean(selfBoolean) == otherInterop.asBoolean(otherBoolean);
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
@Specialization(guards = {
"isTimeZone(selfTimeZone, selfInterop)",
"isTimeZone(otherTimeZone, otherInterop)",
}, limit = "3")
boolean equalsTimeZones(Object selfTimeZone, Object otherTimeZone,
@CachedLibrary("selfTimeZone") InteropLibrary selfInterop,
@CachedLibrary("otherTimeZone") InteropLibrary otherInterop) {
try {
return selfInterop.asTimeZone(selfTimeZone).equals(
otherInterop.asTimeZone(otherTimeZone)
);
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
@TruffleBoundary
@Specialization(guards = {
"isZonedDateTime(selfZonedDateTime, selfInterop)",
"isZonedDateTime(otherZonedDateTime, otherInterop)",
}, limit = "3")
boolean equalsZonedDateTimes(Object selfZonedDateTime, Object otherZonedDateTime,
@CachedLibrary("selfZonedDateTime") InteropLibrary selfInterop,
@CachedLibrary("otherZonedDateTime") InteropLibrary otherInterop) {
try {
var self = ZonedDateTime.of(
selfInterop.asDate(selfZonedDateTime),
selfInterop.asTime(selfZonedDateTime),
selfInterop.asTimeZone(selfZonedDateTime)
);
var other = ZonedDateTime.of(
otherInterop.asDate(otherZonedDateTime),
otherInterop.asTime(otherZonedDateTime),
otherInterop.asTimeZone(otherZonedDateTime)
);
// We cannot use self.isEqual(other), because that does not include timezone.
return self.compareTo(other) == 0;
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
@Specialization(guards = {
"isDateTime(selfDateTime, selfInterop)",
"isDateTime(otherDateTime, otherInterop)",
}, limit = "3")
boolean equalsDateTimes(Object selfDateTime, Object otherDateTime,
@CachedLibrary("selfDateTime") InteropLibrary selfInterop,
@CachedLibrary("otherDateTime") InteropLibrary otherInterop) {
try {
var self = LocalDateTime.of(
selfInterop.asDate(selfDateTime),
selfInterop.asTime(selfDateTime)
);
var other = LocalDateTime.of(
otherInterop.asDate(otherDateTime),
otherInterop.asTime(otherDateTime)
);
return self.isEqual(other);
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
@Specialization(guards = {
"isDate(selfDate, selfInterop)",
"isDate(otherDate, otherInterop)",
}, limit = "3")
boolean equalsDates(Object selfDate, Object otherDate,
@CachedLibrary("selfDate") InteropLibrary selfInterop,
@CachedLibrary("otherDate") InteropLibrary otherInterop) {
try {
return selfInterop.asDate(selfDate).isEqual(
otherInterop.asDate(otherDate)
);
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
@Specialization(guards = {
"isTime(selfTime, selfInterop)",
"isTime(otherTime, otherInterop)",
}, limit = "3")
boolean equalsTimes(Object selfTime, Object otherTime,
@CachedLibrary("selfTime") InteropLibrary selfInterop,
@CachedLibrary("otherTime") InteropLibrary otherInterop) {
try {
return selfInterop.asTime(selfTime).equals(
otherInterop.asTime(otherTime)
);
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
@Specialization(guards = {
"selfInterop.isDuration(selfDuration)",
"otherInterop.isDuration(otherDuration)"
}, limit = "3")
boolean equalsDuration(Object selfDuration, Object otherDuration,
@CachedLibrary("selfDuration") InteropLibrary selfInterop,
@CachedLibrary("otherDuration") InteropLibrary otherInterop) {
try {
return selfInterop.asDuration(selfDuration).equals(
otherInterop.asDuration(otherDuration)
);
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
/**
* Compares interop strings according to the lexicographical order, handling Unicode
* normalization. See {@code Text_Utils.compare_to}.
@ -504,319 +221,46 @@ public abstract class EqualsNode extends Node {
) == 0;
}
@Specialization(guards = {
"selfInterop.hasArrayElements(selfArray)",
"otherInterop.hasArrayElements(otherArray)",
"!selfInterop.hasHashEntries(selfArray)",
"!otherInterop.hasHashEntries(otherArray)",
}, limit = "3")
boolean equalsArrays(Object selfArray, Object otherArray,
@CachedLibrary("selfArray") InteropLibrary selfInterop,
@CachedLibrary("otherArray") InteropLibrary otherInterop,
@Cached EqualsNode equalsNode,
@Cached HasCustomComparatorNode hasCustomComparatorNode,
@Cached InvokeAnyEqualsNode invokeAnyEqualsNode
) {
try {
long selfSize = selfInterop.getArraySize(selfArray);
if (selfSize != otherInterop.getArraySize(otherArray)) {
return false;
}
for (long i = 0; i < selfSize; i++) {
Object selfElem = selfInterop.readArrayElement(selfArray, i);
Object otherElem = otherInterop.readArrayElement(otherArray, i);
boolean elemsAreEqual;
if (selfElem instanceof Atom selfAtomElem
&& otherElem instanceof Atom otherAtomElem
&& hasCustomComparatorNode.execute(selfAtomElem)) {
elemsAreEqual = invokeAnyEqualsNode.execute(selfAtomElem, otherAtomElem);
} else {
elemsAreEqual = equalsNode.execute(selfElem, otherElem);
}
if (!elemsAreEqual) {
return false;
}
}
return true;
} catch (UnsupportedMessageException | InvalidArrayIndexException e) {
throw new IllegalStateException(e);
}
}
@Specialization(guards = {
"selfInterop.hasHashEntries(selfHashMap)",
"otherInterop.hasHashEntries(otherHashMap)",
"!selfInterop.hasArrayElements(selfHashMap)",
"!otherInterop.hasArrayElements(otherHashMap)"
}, limit = "3")
boolean equalsHashMaps(Object selfHashMap, Object otherHashMap,
@CachedLibrary("selfHashMap") InteropLibrary selfInterop,
@CachedLibrary("otherHashMap") InteropLibrary otherInterop,
@CachedLibrary(limit = "5") InteropLibrary entriesInterop,
@Cached EqualsNode equalsNode) {
try {
int selfHashSize = (int) selfInterop.getHashSize(selfHashMap);
int otherHashSize = (int) otherInterop.getHashSize(otherHashMap);
if (selfHashSize != otherHashSize) {
return false;
}
Object selfEntriesIter = selfInterop.getHashEntriesIterator(selfHashMap);
while (entriesInterop.hasIteratorNextElement(selfEntriesIter)) {
Object selfKeyValue = entriesInterop.getIteratorNextElement(selfEntriesIter);
Object key = entriesInterop.readArrayElement(selfKeyValue, 0);
Object selfValue = entriesInterop.readArrayElement(selfKeyValue, 1);
if (otherInterop.isHashEntryExisting(otherHashMap, key)
&& otherInterop.isHashEntryReadable(otherHashMap, key)) {
Object otherValue = otherInterop.readHashValue(otherHashMap, key);
if (!equalsNode.execute(selfValue, otherValue)) {
return false;
}
} else {
return false;
}
}
return true;
} catch (UnsupportedMessageException | StopIterationException | UnknownKeyException |
InvalidArrayIndexException e) {
throw new IllegalStateException(e);
}
}
@Specialization(guards = {
"isObjectWithMembers(selfObject, interop)",
"isObjectWithMembers(otherObject, interop)",
})
boolean equalsInteropObjectWithMembers(Object selfObject, Object otherObject,
@CachedLibrary(limit = "10") InteropLibrary interop,
@CachedLibrary(limit = "5") TypesLibrary typesLib,
@Cached EqualsNode equalsNode) {
try {
Object selfMembers = interop.getMembers(selfObject);
Object otherMembers = interop.getMembers(otherObject);
assert interop.getArraySize(selfMembers) < Integer.MAX_VALUE : "Long array sizes not supported";
int membersSize = (int) interop.getArraySize(selfMembers);
if (interop.getArraySize(otherMembers) != membersSize) {
return false;
}
// Check member names
String[] memberNames = new String[membersSize];
for (int i = 0; i < membersSize; i++) {
String selfMemberName = interop.asString(interop.readArrayElement(selfMembers, i));
String otherMemberName = interop.asString(interop.readArrayElement(otherMembers, i));
if (!equalsNode.execute(selfMemberName, otherMemberName)) {
return false;
}
memberNames[i] = selfMemberName;
}
// Check member values
for (int i = 0; i < membersSize; i++) {
if (interop.isMemberReadable(selfObject, memberNames[i]) &&
interop.isMemberReadable(otherObject, memberNames[i])) {
Object selfMember = interop.readMember(selfObject, memberNames[i]);
Object otherMember = interop.readMember(otherObject, memberNames[i]);
if (!equalsNode.execute(selfMember, otherMember)) {
return false;
}
}
}
return true;
} catch (UnsupportedMessageException | InvalidArrayIndexException | UnknownIdentifierException e) {
throw new IllegalStateException(
String.format("One of the interop objects has probably wrongly specified interop API "
+ "for members. selfObject = %s ; otherObject = %s", selfObject, otherObject),
e
);
}
@Specialization(
guards = "isPrimitive(self, strings) != isPrimitive(other, strings)"
)
boolean equalsDifferent(Object self, Object other, @CachedLibrary(limit = "10") InteropLibrary strings) {
return false;
}
/** Equals for Atoms and AtomConstructors */
@Specialization
boolean equalsAtomConstructors(AtomConstructor selfConstructor, AtomConstructor otherConstructor) {
return selfConstructor == otherConstructor;
boolean equalsAtomConstructors(AtomConstructor self, AtomConstructor other) {
return self == other;
}
static EqualsNode[] createEqualsNodes(int size) {
EqualsNode[] nodes = new EqualsNode[size];
Arrays.fill(nodes, EqualsNode.build());
return nodes;
@Specialization
boolean equalsAtoms(Atom self, Atom other, @Cached EqualsAtomNode equalsNode) {
return equalsNode.execute(self, other);
}
@Specialization(guards = {
"selfCtorCached == self.getConstructor()"
}, limit = "10")
@ExplodeLoop
boolean equalsAtoms(
Atom self,
Atom other,
@Cached("self.getConstructor()") AtomConstructor selfCtorCached,
@Cached(value = "selfCtorCached.getFields().length", allowUncached = true) int fieldsLenCached,
@Cached(value = "createEqualsNodes(fieldsLenCached)", allowUncached = true) EqualsNode[] fieldEqualsNodes,
@Cached ConditionProfile constructorsNotEqualProfile,
@Cached HasCustomComparatorNode hasCustomComparatorNode,
@Cached InvokeAnyEqualsNode invokeAnyEqualsNode,
@CachedLibrary(limit = "5") StructsLibrary structsLib
@Specialization(guards = "isNotPrimitive(self, other, strings, warnings)")
boolean equalsComplex(
Object self, Object other,
@Cached EqualsComplexNode complex,
@CachedLibrary(limit = "10") InteropLibrary strings,
@CachedLibrary(limit = "10") WarningsLibrary warnings
) {
if (constructorsNotEqualProfile.profile(
self.getConstructor() != other.getConstructor()
)) {
return false;
}
var selfFields = structsLib.getFields(self);
var otherFields = structsLib.getFields(other);
assert selfFields.length == otherFields.length : "Constructors are same, atoms should have the same number of fields";
CompilerAsserts.partialEvaluationConstant(fieldsLenCached);
for (int i = 0; i < fieldsLenCached; i++) {
boolean fieldsAreEqual;
// We don't check whether `other` has the same type of comparator, that is checked in
// `Any.==` that we invoke here anyway.
if (selfFields[i] instanceof Atom selfAtomField
&& otherFields[i] instanceof Atom otherAtomField
&& hasCustomComparatorNode.execute(selfAtomField)) {
// If selfFields[i] has a custom comparator, we delegate to `Any.==` that deals with
// custom comparators. EqualsNode cannot deal with custom comparators.
fieldsAreEqual = invokeAnyEqualsNode.execute(selfAtomField, otherAtomField);
} else {
fieldsAreEqual = fieldEqualsNodes[i].execute(
selfFields[i],
otherFields[i]
);
}
if (!fieldsAreEqual) {
return false;
}
}
return true;
return complex.execute(self, other);
}
@TruffleBoundary
@Specialization(replaces = "equalsAtoms")
boolean equalsAtomsUncached(Atom self, Atom other) {
if (!equalsAtomConstructors(self.getConstructor(), other.getConstructor())) {
static boolean isNotPrimitive(Object a, Object b, InteropLibrary strings, WarningsLibrary warnings) {
if (a instanceof AtomConstructor && b instanceof AtomConstructor) {
return false;
}
Object[] selfFields = StructsLibrary.getUncached().getFields(self);
Object[] otherFields = StructsLibrary.getUncached().getFields(other);
if (selfFields.length != otherFields.length) {
if (a instanceof Atom && b instanceof Atom) {
return false;
}
for (int i = 0; i < selfFields.length; i++) {
boolean areFieldsSame;
if (selfFields[i] instanceof Atom selfFieldAtom
&& otherFields[i] instanceof Atom otherFieldAtom
&& HasCustomComparatorNode.getUncached().execute(selfFieldAtom)) {
areFieldsSame = InvokeAnyEqualsNode.getUncached().execute(selfFieldAtom, otherFieldAtom);
} else {
areFieldsSame = EqualsNodeGen.getUncached().execute(selfFields[i], otherFields[i]);
}
if (!areFieldsSame) {
return false;
}
if (warnings.hasWarnings(a) || warnings.hasWarnings(b)) {
return true;
}
return true;
}
@Specialization(guards = {
"isHostObject(selfHostObject)",
"isHostObject(otherHostObject)"
})
boolean equalsHostObjects(
Object selfHostObject, Object otherHostObject,
@CachedLibrary(limit = "5") InteropLibrary interop
) {
try {
return interop.asBoolean(
interop.invokeMember(selfHostObject, "equals", otherHostObject)
);
} catch (UnsupportedMessageException | ArityException | UnknownIdentifierException |
UnsupportedTypeException e) {
throw new IllegalStateException(e);
}
}
// HostFunction is identified by a qualified name, it is not a lambda.
// It has well-defined equality based on the qualified name.
@Specialization(guards = {
"isHostFunction(selfHostFunc)",
"isHostFunction(otherHostFunc)"
})
boolean equalsHostFunctions(Object selfHostFunc, Object otherHostFunc,
@CachedLibrary(limit = "5") InteropLibrary interop,
@Cached EqualsNode equalsNode) {
Object selfFuncStrRepr = interop.toDisplayString(selfHostFunc);
Object otherFuncStrRepr = interop.toDisplayString(otherHostFunc);
return equalsNode.execute(selfFuncStrRepr, otherFuncStrRepr);
}
@Specialization(guards = "fallbackGuard(left, right, interop, warningsLib)")
@TruffleBoundary
boolean equalsGeneric(Object left, Object right,
@CachedLibrary(limit = "10") InteropLibrary interop,
@CachedLibrary(limit = "10") TypesLibrary typesLib,
@CachedLibrary(limit = "10") WarningsLibrary warningsLib) {
return left == right
|| interop.isIdentical(left, right, interop)
|| left.equals(right)
|| (isNullOrNothing(left, typesLib, interop) && isNullOrNothing(right, typesLib, interop));
}
// We have to manually specify negation of guards of other specializations, because
// we cannot use @Fallback here. Note that this guard is not precisely the negation of
// all the other guards on purpose.
boolean fallbackGuard(Object left, Object right, InteropLibrary interop, WarningsLibrary warnings) {
if (isPrimitive(left) && isPrimitive(right)) {
return false;
}
if (isHostObject(left) && isHostObject(right)) {
return false;
}
if (isHostFunction(left) && isHostFunction(right)) {
return false;
}
if (left instanceof Atom && right instanceof Atom) {
return false;
}
if (interop.isNull(left) && interop.isNull(right)) {
return false;
}
if (interop.isString(left) && interop.isString(right)) {
return false;
}
if (interop.hasArrayElements(left) && interop.hasArrayElements(right)) {
return false;
}
if (interop.hasHashEntries(left) && interop.hasHashEntries(right)) {
return false;
}
if (isObjectWithMembers(left, interop) && isObjectWithMembers(right, interop)) {
return false;
}
if (isTimeZone(left, interop) && isTimeZone(right, interop)) {
return false;
}
if (isZonedDateTime(left, interop) && isZonedDateTime(right, interop)) {
return false;
}
if (isDateTime(left, interop) && isDateTime(right, interop)) {
return false;
}
if (isDate(left, interop) && isDate(right, interop)) {
return false;
}
if (isTime(left, interop) && isTime(right, interop)) {
return false;
}
if (interop.isDuration(left) && interop.isDuration(right)) {
return false;
}
if (warnings.hasWarnings(left) || warnings.hasWarnings(right)) {
return false;
}
// For all other cases, fall through to the generic specialization
return true;
return !isPrimitive(a, strings) && !isPrimitive(b, strings);
}
/**
@ -826,131 +270,12 @@ public abstract class EqualsNode extends Node {
* All the primitive types should be handled in their corresponding specializations.
* See {@link org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode}.
*/
private static boolean isPrimitive(Object object) {
static boolean isPrimitive(Object object, InteropLibrary strings) {
return object instanceof Boolean ||
object instanceof Long ||
object instanceof Double ||
object instanceof EnsoBigInteger ||
object instanceof Text;
}
boolean isTimeZone(Object object, InteropLibrary interop) {
return
!interop.isTime(object) &&
!interop.isDate(object) &&
interop.isTimeZone(object);
}
boolean isZonedDateTime(Object object, InteropLibrary interop) {
return
interop.isTime(object) &&
interop.isDate(object) &&
interop.isTimeZone(object);
}
boolean isDateTime(Object object, InteropLibrary interop) {
return
interop.isTime(object) &&
interop.isDate(object) &&
!interop.isTimeZone(object);
}
boolean isDate(Object object, InteropLibrary interop) {
return
!interop.isTime(object) &&
interop.isDate(object) &&
!interop.isTimeZone(object);
}
boolean isTime(Object object, InteropLibrary interop) {
return
interop.isTime(object) &&
!interop.isDate(object) &&
!interop.isTimeZone(object);
}
boolean isObjectWithMembers(Object object, InteropLibrary interop) {
if (object instanceof Atom) {
return false;
}
if (isHostObject(object)) {
return false;
}
if (interop.isDate(object)) {
return false;
}
if (interop.isTime(object)) {
return false;
}
return interop.hasMembers(object);
}
private boolean isNullOrNothing(Object object, TypesLibrary typesLib, InteropLibrary interop) {
if (typesLib.hasType(object)) {
return typesLib.getType(object) == EnsoContext.get(this).getNothing();
} else if (interop.isNull(object)) {
return true;
} else {
return object == null;
}
}
@TruffleBoundary
boolean isHostObject(Object object) {
return EnsoContext.get(this).getEnvironment().isHostObject(object);
}
@TruffleBoundary
boolean isHostFunction(Object object) {
return EnsoContext.get(this).getEnvironment().isHostFunction(object);
}
/**
* Helper node for invoking `Any.==` method.
*/
@GenerateUncached
static abstract class InvokeAnyEqualsNode extends Node {
static InvokeAnyEqualsNode getUncached() {
return EqualsNodeGen.InvokeAnyEqualsNodeGen.getUncached();
}
abstract boolean execute(Atom selfAtom, Atom otherAtom);
@Specialization
boolean invokeEqualsCachedAtomCtor(Atom selfAtom, Atom thatAtom,
@Cached(value = "getAnyEqualsMethod()", allowUncached = true) Function anyEqualsFunc,
@Cached(value = "buildInvokeFuncNodeForAnyEquals()", allowUncached = true) InvokeFunctionNode invokeAnyEqualsNode,
@CachedLibrary(limit = "3") InteropLibrary interop) {
Object ret = invokeAnyEqualsNode.execute(
anyEqualsFunc,
null,
State.create(EnsoContext.get(this)),
// TODO: Shouldn't Any type be the very first argument? (synthetic self)?
new Object[]{selfAtom, thatAtom}
);
try {
return interop.asBoolean(ret);
} catch (UnsupportedMessageException e) {
throw new IllegalStateException("Return value from Any== should be Boolean", e);
}
}
@TruffleBoundary
Function getAnyEqualsMethod() {
var anyType = EnsoContext.get(this).getBuiltins().any();
Function anyEqualsFunc =
anyType.getDefinitionScope().getMethods().get(anyType).get("==");
assert anyEqualsFunc != null : "Any.== method must exist";
return anyEqualsFunc;
}
InvokeFunctionNode buildInvokeFuncNodeForAnyEquals() {
return InvokeFunctionNode.build(
new CallArgumentInfo[]{new CallArgumentInfo("self"), new CallArgumentInfo("that")},
DefaultsExecutionMode.EXECUTE,
ArgumentsExecutionMode.EXECUTE
);
}
object instanceof Text ||
strings.isString(object);
}
}

View File

@ -28,7 +28,7 @@ import java.util.Arrays;
import org.enso.interpreter.dsl.AcceptsError;
import org.enso.interpreter.dsl.BuiltinMethod;
import org.enso.interpreter.node.expression.builtin.number.utils.BigIntegerOps;
import org.enso.interpreter.node.expression.builtin.ordering.HasCustomComparatorNode;
import org.enso.interpreter.node.expression.builtin.ordering.CustomComparatorNode;
import org.enso.interpreter.node.expression.builtin.ordering.HashCallbackNode;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.Module;
@ -205,7 +205,7 @@ public abstract class HashCodeNode extends Node {
HashCodeNode[] fieldHashCodeNodes,
@Cached ConditionProfile isHashCodeCached,
@CachedLibrary(limit = "10") StructsLibrary structs,
@Cached HasCustomComparatorNode hasCustomComparatorNode,
@Cached CustomComparatorNode customComparatorNode,
@Cached HashCallbackNode hashCallbackNode) {
if (isHashCodeCached.profile(atom.getHashCode() != null)) {
return atom.getHashCode();
@ -218,7 +218,7 @@ public abstract class HashCodeNode extends Node {
// hashes stores hash codes for all fields, and for constructor.
int[] hashes = new int[fieldsCount + 1];
for (int i = 0; i < fieldsLenCached; i++) {
if (fields[i] instanceof Atom atomField && hasCustomComparatorNode.execute(atomField)) {
if (fields[i] instanceof Atom atomField && customComparatorNode.execute(atomField) != null) {
hashes[i] = (int) hashCallbackNode.execute(atomField);
} else {
hashes[i] = (int) fieldHashCodeNodes[i].execute(fields[i]);
@ -244,7 +244,7 @@ public abstract class HashCodeNode extends Node {
int[] hashes = new int[fields.length + 1];
for (int i = 0; i < fields.length; i++) {
if (fields[i] instanceof Atom atomField
&& HasCustomComparatorNode.getUncached().execute(atomField)) {
&& CustomComparatorNode.getUncached().execute(atomField) != null) {
hashes[i] = (int) HashCallbackNode.getUncached().execute(atomField);
} else {
hashes[i] = (int) HashCodeNodeGen.getUncached().execute(fields[i]);
@ -425,7 +425,7 @@ public abstract class HashCodeNode extends Node {
@Cached HashCodeNode hashCodeNode,
@Cached("createCountingProfile()") LoopConditionProfile loopProfile,
@Cached HashCallbackNode hashCallbackNode,
@Cached HasCustomComparatorNode hasCustomComparatorNode) {
@Cached CustomComparatorNode customComparatorNode) {
try {
long arraySize = interop.getArraySize(selfArray);
loopProfile.profileCounted(arraySize);
@ -433,7 +433,7 @@ public abstract class HashCodeNode extends Node {
for (int i = 0; loopProfile.inject(i < arraySize); i++) {
if (interop.isArrayElementReadable(selfArray, i)) {
Object elem = interop.readArrayElement(selfArray, i);
if (elem instanceof Atom atomElem && hasCustomComparatorNode.execute(atomElem)) {
if (elem instanceof Atom atomElem && customComparatorNode.execute(atomElem) != null) {
elemHashCodes[i] = (int) hashCallbackNode.execute(atomElem);
} else {
elemHashCodes[i] = (int) hashCodeNode.execute(elem);

View File

@ -0,0 +1,64 @@
package org.enso.interpreter.node.expression.builtin.ordering;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.node.callable.InvokeCallableNode.ArgumentsExecutionMode;
import org.enso.interpreter.node.callable.InvokeCallableNode.DefaultsExecutionMode;
import org.enso.interpreter.node.callable.InvokeConversionNode;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.callable.UnresolvedConversion;
import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.data.Type;
import org.enso.interpreter.runtime.state.State;
/**
* Helper node for invocation of {@code Comparable.has_custom_comparator atom}. Note that emulating
* the semantics of that function in Java code would be too complicated, so we rather implemented it
* in Enso and just call it from this node.
*/
@GenerateUncached
public abstract class CustomComparatorNode extends Node {
public static CustomComparatorNode getUncached() {
return CustomComparatorNodeGen.getUncached();
}
/**
* Returns the given atom's comparator if it is a comparator that is different
* than the default (internal) one.
*
* @param atom Atom for which we check whether it has custom comparator
* @return {@code null} if the atom has default comparator. Otherwise it returns the real comparator type.
*/
public abstract Type execute(Atom atom);
@Specialization
Type hasCustomComparatorCached(
Atom atom,
@Cached(value = "buildConvertionNode()", allowUncached = true) InvokeConversionNode convertNode,
@Cached(value = "createConversion()", allowUncached = true) UnresolvedConversion conversion
) {
var ctx = EnsoContext.get(this);
var comparableType = ctx.getBuiltins().comparable().getType();
var state = State.create(ctx);
Object res = convertNode.execute(null, state, conversion, comparableType, atom, new Object[] { comparableType, atom });
return res instanceof Type result && result != ctx.getBuiltins().defaultComparator().getType() ? result : null;
}
UnresolvedConversion createConversion() {
var ctx = EnsoContext.get(this);
var comparableType = ctx.getBuiltins().comparable().getType();
return UnresolvedConversion.build(comparableType.getDefinitionScope());
}
static InvokeConversionNode buildConvertionNode() {
CallArgumentInfo[] argSchema = new CallArgumentInfo[2];
argSchema[0] = new CallArgumentInfo();
argSchema[1] = new CallArgumentInfo();
return InvokeConversionNode.build(argSchema, DefaultsExecutionMode.EXECUTE, ArgumentsExecutionMode.EXECUTE, 1);
}
}

View File

@ -1,85 +0,0 @@
package org.enso.interpreter.node.expression.builtin.ordering;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.Node;
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.callable.argument.CallArgumentInfo;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.state.State;
/**
* Helper node for invocation of {@code Comparable.has_custom_comparator atom}. Note that emulating
* the semantics of that function in Java code would be too complicated, so we rather implemented it
* in Enso and just call it from this node.
*/
@GenerateUncached
public abstract class HasCustomComparatorNode extends Node {
public static HasCustomComparatorNode getUncached() {
return HasCustomComparatorNodeGen.getUncached();
}
/**
* Returns true if the given atom has a custom comparator, that is a comparator that is different
* than the default (internal) ones.
*
* @param atom Atom for which we check whether it has custom comparator
* @return true iff the given atom has a custom comparator
*/
public abstract boolean execute(Atom atom);
@Specialization
boolean hasCustomComparatorCached(
Atom atom,
@Cached(value = "getHasCustomComparatorFunction()", allowUncached = true)
Function hasCustomComparatorFunc,
@Cached(value = "buildInvokeNodeWithAtomArgument()", allowUncached = true)
InvokeFunctionNode hasCustomComparatorInvokeNode,
@CachedLibrary(limit = "5") InteropLibrary interop) {
var ctx = EnsoContext.get(this);
var comparableType = ctx.getBuiltins().comparable().getType();
Object res =
hasCustomComparatorInvokeNode.execute(
hasCustomComparatorFunc, null, State.create(ctx), new Object[] {comparableType, atom});
assert interop.isBoolean(res);
try {
return interop.asBoolean(res);
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(
"Return type from Comparable.has_custom_comparator should be Boolean", e);
}
}
/**
* Builds an {@link InvokeFunctionNode} for a method with just one argument named {@code atom}.
*/
static InvokeFunctionNode buildInvokeNodeWithAtomArgument() {
return InvokeFunctionNode.build(
new CallArgumentInfo[] {new CallArgumentInfo("self"), new CallArgumentInfo("atom")},
DefaultsExecutionMode.EXECUTE,
ArgumentsExecutionMode.EXECUTE);
}
@TruffleBoundary
Function getHasCustomComparatorFunction() {
var comparableType = EnsoContext.get(this).getBuiltins().comparable().getType();
Function hasCustomComparatorFunc =
comparableType
.getDefinitionScope()
.getMethods()
.get(comparableType)
.get("has_custom_comparator");
assert hasCustomComparatorFunc != null : "Comparable.has_custom_comparator function must exist";
return hasCustomComparatorFunc;
}
}

View File

@ -185,7 +185,7 @@ public class EqualsTest extends TestBase {
public void testVectorsEquality() {
Object ensoVector =
unwrapValue(context, createValue(context, "[1,2,3]", "from Standard.Base.import all"));
Object javaVector = unwrapValue(context, context.asValue(List.of(1, 2, 3)));
Object javaVector = unwrapValue(context, context.asValue(List.of(1L, 2L, 3L)));
executeInContext(
context,
() -> {

View File

@ -6,7 +6,7 @@ type Any
to_text self = @Builtin_Method "Any.to_text"
to_display_text self = @Builtin_Method "Any.to_display_text"
is_error self = False
== self other = Comparable.equals_builtin self other
== self other = @Builtin_Method "Any.=="
!= self other = (self == other).not
< self other = Comparable.less_than_builtin self other
<= self other = Comparable.less_than_builtin self other || Comparable.equals_builtin self other