mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 10:42:05 +03:00
Splitting Gigantic EqualsNode into few Smaller Ones (#6280)
Splitting gigantic `EqualsNode` into few smaller ones
This commit is contained in:
parent
b020e7f97b
commit
413661b366
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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,
|
||||
() -> {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user