diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/TypeChecker.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/TypeChecker.java index e81dcdca86cc..f5727580ab9b 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/TypeChecker.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/TypeChecker.java @@ -30,7 +30,6 @@ import io.ballerina.runtime.api.types.UnionType; import io.ballerina.runtime.api.types.XmlNodeType; import io.ballerina.runtime.api.utils.StringUtils; -import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BDecimal; import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BObject; @@ -75,6 +74,7 @@ import io.ballerina.runtime.internal.values.TupleValueImpl; import io.ballerina.runtime.internal.values.TypedescValue; import io.ballerina.runtime.internal.values.TypedescValueImpl; +import io.ballerina.runtime.internal.values.ValuePair; import io.ballerina.runtime.internal.values.XmlComment; import io.ballerina.runtime.internal.values.XmlItem; import io.ballerina.runtime.internal.values.XmlPi; @@ -376,7 +376,7 @@ public static Type getType(Object value) { * @return True if values are equal, else false. */ public static boolean isEqual(Object lhsValue, Object rhsValue) { - return isEqual(lhsValue, rhsValue, new ArrayList<>()); + return isEqual(lhsValue, rhsValue, new HashSet<>()); } /** @@ -416,7 +416,7 @@ private static boolean isDecimalRealNumber(DecimalValue decimalValue) { /** * Reference equality check for values. If both the values are simple basic types, returns the same - * result as {@link #isEqual(Object, Object, List)} + * result as {@link #isEqual(Object, Object, Set)} * * @param lhsValue The value on the left hand side * @param rhsValue The value on the right hand side @@ -471,7 +471,7 @@ public static boolean isReferenceEqual(Object lhsValue, Object rhsValue) { lhsType.getName().equals(rhsType.getName()) && rhsType.equals(lhsType); default: if (lhsValue instanceof RegExpValue && rhsValue instanceof RegExpValue) { - return isEqual((RegExpValue) lhsValue, (RegExpValue) rhsValue); + return ((RegExpValue) lhsValue).equals(rhsValue, new HashSet<>()); } return false; } @@ -552,10 +552,10 @@ public static Object getAnnotValue(TypedescValue typedescValue, BString annotTag * * @param sourceType type to check * @param targetType type to compare with - * @return flag indicating the the equivalence of the two types + * @return flag indicating the equivalence of the two types */ public static boolean checkIsType(Type sourceType, Type targetType) { - return checkIsType(sourceType, targetType, (List) null); + return checkIsType(sourceType, targetType, null); } @Deprecated @@ -2474,11 +2474,10 @@ private static boolean isLikeAnydataType(Object[] objects, List u private static boolean checkIsLikeTupleType(Object sourceValue, BTupleType targetType, List unresolvedValues, boolean allowNumericConversion) { - if (!(sourceValue instanceof ArrayValue)) { + if (!(sourceValue instanceof ArrayValue source)) { return false; } - ArrayValue source = (ArrayValue) sourceValue; List targetTypes = targetType.getTupleTypes(); int sourceTypeSize = source.size(); int targetTypeSize = targetTypes.size(); @@ -2933,7 +2932,7 @@ static boolean isSimpleBasicType(Type type) { * @param checkedValues Structured value pairs already compared or being compared * @return True if values are equal, else false. */ - private static boolean isEqual(Object lhsValue, Object rhsValue, List checkedValues) { + public static boolean isEqual(Object lhsValue, Object rhsValue, Set checkedValues) { if (lhsValue == rhsValue) { return true; } @@ -2945,7 +2944,7 @@ private static boolean isEqual(Object lhsValue, Object rhsValue, List return checkValueEquals(lhsValue, rhsValue, checkedValues, getType(lhsValue), getType(rhsValue)); } - private static boolean checkValueEquals(Object lhsValue, Object rhsValue, List checkedValues, + private static boolean checkValueEquals(Object lhsValue, Object rhsValue, Set checkedValues, Type lhsValType, Type rhsValType) { lhsValType = getImpliedType(lhsValType); rhsValType = getImpliedType(rhsValType); @@ -2982,39 +2981,36 @@ private static boolean checkValueEquals(Object lhsValue, Object rhsValue, List checkedValues) { - ValuePair compValuePair = new ValuePair(lhsList, rhsList); - if (checkedValues.contains(compValuePair)) { - return true; - } - checkedValues.add(compValuePair); - - if (lhsList.size() != rhsList.size()) { - return false; - } - - for (int i = 0; i < lhsList.size(); i++) { - if (!isEqual(lhsList.get(i), rhsList.get(i), checkedValues)) { - return false; - } - } - return true; - } - - /** - * Deep equality check for a map. - * - * @param lhsMap Map on the left hand side - * @param rhsMap Map on the right hand side - * @param checkedValues Structured value pairs already compared or being compared - * @return True if the map values are equal, else false. - */ - private static boolean isEqual(MapValueImpl lhsMap, MapValueImpl rhsMap, List checkedValues) { - ValuePair compValuePair = new ValuePair(lhsMap, rhsMap); - if (checkedValues.contains(compValuePair)) { - return true; - } - checkedValues.add(compValuePair); - - if (lhsMap.size() != rhsMap.size()) { - return false; - } - - if (!lhsMap.keySet().containsAll(rhsMap.keySet())) { - return false; - } - - Iterator> mapIterator = lhsMap.entrySet().iterator(); - while (mapIterator.hasNext()) { - Map.Entry lhsMapEntry = mapIterator.next(); - if (!isEqual(lhsMapEntry.getValue(), rhsMap.get(lhsMapEntry.getKey()), checkedValues)) { - return false; - } - } - return true; - } - - /** - * Deep equality check for a table. - * - * @param lhsTable Table on the left hand side - * @param rhsTable Table on the right hand side - * @param checkedValues Structured value pairs already compared or being compared - * @return True if the table values are equal, else false. - */ - private static boolean isEqual(TableValueImpl lhsTable, TableValueImpl rhsTable, List checkedValues) { - ValuePair compValuePair = new ValuePair(lhsTable, rhsTable); - if (checkedValues.contains(compValuePair)) { - return true; - } - checkedValues.add(compValuePair); - - if (lhsTable.size() != rhsTable.size()) { - return false; - } - - boolean isLhsKeyedTable = - ((BTableType) getImpliedType(lhsTable.getType())).getFieldNames().length > 0; - boolean isRhsKeyedTable = - ((BTableType) getImpliedType(rhsTable.getType())).getFieldNames().length > 0; - - Object[] lhsTableValues = lhsTable.values().toArray(); - Object[] rhsTableValues = rhsTable.values().toArray(); - - if (isLhsKeyedTable == isRhsKeyedTable) { - for (int i = 0; i < lhsTableValues.length; i++) { - if (!isEqual(lhsTableValues[i], rhsTableValues[i], checkedValues)) { - return false; - } - } - return true; - } - - return false; - } - - /** - * Deep equality check for regular expressions. - * - * @param lhsRegExp Regular expression on the left hand side - * @param rhsRegExp Regular expression on the right hand side - * @return True if the regular expression values are equal, else false. - */ - private static boolean isEqual(RegExpValue lhsRegExp, RegExpValue rhsRegExp) { - return lhsRegExp.stringValue(null).equals(rhsRegExp.stringValue(null)); - } - - /** - * Deep equality check for error. - * - * @param lhsError The error on the left hand side - * @param rhsError The error on the right hand side - * @param checkedValues Errors already compared or being compared - * @return True if the error values are equal, else false. - */ - private static boolean isEqual(ErrorValue lhsError, ErrorValue rhsError, List checkedValues) { - ValuePair compValuePair = new ValuePair(lhsError, rhsError); - if (checkedValues.contains(compValuePair)) { - return true; - } - checkedValues.add(compValuePair); - - return isEqual(lhsError.getMessage(), rhsError.getMessage(), checkedValues) && - isEqual((MapValueImpl) lhsError.getDetails(), (MapValueImpl) rhsError.getDetails(), checkedValues) && - isEqual(lhsError.getCause(), rhsError.getCause(), checkedValues); - } - - /** - * Deep equality check for XML Sequence. - * - * @param lhsXMLSequence The XML sequence on the left hand side - * @param rhsXml The XML on the right hand side - * @return True if the XML values are equal, else false. - */ - private static boolean isEqual(XmlSequence lhsXMLSequence, XmlValue rhsXml) { - if (rhsXml instanceof XmlSequence) { - XmlSequence rhsXMLSequence = (XmlSequence) rhsXml; - return isXMLSequenceChildrenEqual(lhsXMLSequence.getChildrenList(), rhsXMLSequence.getChildrenList()); - } - if (rhsXml instanceof XmlItem) { - return lhsXMLSequence.getChildrenList().size() == 1 && - isEqual(lhsXMLSequence.getChildrenList().get(0), rhsXml); - } - return lhsXMLSequence.getChildrenList().isEmpty() && - TypeUtils.getType(rhsXml) == PredefinedTypes.TYPE_XML_NEVER; - } - - /** - * Deep equality check for XML item. - * - * @param lhsXMLItem The XML item on the left hand side - * @param rhsXml The XML on the right hand side - * @return True if the XML values are equal, else false. - */ - private static boolean isEqual(XmlItem lhsXMLItem, XmlValue rhsXml) { - if (rhsXml instanceof XmlItem) { - XmlItem rhsXMLItem = (XmlItem) rhsXml; - if (!(rhsXMLItem.getQName().equals(lhsXMLItem.getQName()))) { - return false; - } - if (!(rhsXMLItem.getAttributesMap().entrySet().equals(lhsXMLItem.getAttributesMap().entrySet()))) { - return false; - } - return isEqual(rhsXMLItem.getChildrenSeq(), lhsXMLItem.getChildrenSeq()); - } - if (rhsXml instanceof XmlSequence) { - XmlSequence rhsXMLSequence = (XmlSequence) rhsXml; - return rhsXMLSequence.getChildrenList().size() == 1 && - isEqual(lhsXMLItem, rhsXMLSequence.getChildrenList().get(0)); - } - return false; - } - - /** - * Deep equality check for XML Text. - * - * @param lhsXMLText The XML text on the left hand side - * @param rhsXml The XML on the right hand side - * @return True if the XML values are equal, else false. - */ - private static boolean isEqual(XmlText lhsXMLText, XmlValue rhsXml) { - if (rhsXml instanceof XmlText) { - XmlText rhsXMLText = (XmlText) rhsXml; - return lhsXMLText.getTextValue().equals(rhsXMLText.getTextValue()); - } - return lhsXMLText.getType() == PredefinedTypes.TYPE_XML_NEVER && rhsXml instanceof XmlSequence && - ((XmlSequence) rhsXml).getChildrenList().isEmpty(); - } - - /** - * Deep equality check for XML Comment. - * - * @param lhsXMLComment The XML comment on the left hand side - * @param rhsXml The XML on the right hand side - * @return True if the XML values are equal, else false. - */ - private static boolean isEqual(XmlComment lhsXMLComment, XmlValue rhsXml) { - if (!(rhsXml instanceof XmlComment)) { - return false; - } - XmlComment rhXMLComment = (XmlComment) rhsXml; - return lhsXMLComment.getTextValue().equals(rhXMLComment.getTextValue()); - } - - /** - * Deep equality check for XML Processing Instruction. - * - * @param lhsXMLPi The XML processing instruction on the left hand side - * @param rhsXml The XML on the right hand side - * @return True if the XML values are equal, else false. - */ - private static boolean isEqual(XmlPi lhsXMLPi, XmlValue rhsXml) { - if (!(rhsXml instanceof XmlPi)) { - return false; - } - XmlPi rhsXMLPi = (XmlPi) rhsXml; - return lhsXMLPi.getData().equals(rhsXMLPi.getData()) && lhsXMLPi.getTarget().equals(rhsXMLPi.getTarget()); - } - - private static boolean isXMLSequenceChildrenEqual(List lhsList, List rhsList) { - if (lhsList.size() != rhsList.size()) { - return false; - } - - for (int i = 0; i < lhsList.size(); i++) { - if (!isEqual(lhsList.get(i), rhsList.get(i))) { - return false; - } - } - return true; - } - static boolean isRegExpType(Type targetType) { if (targetType.getTag() == TypeTags.TYPE_REFERENCED_TYPE_TAG) { Type referredType = ((BTypeReferenceType) targetType).getReferredType(); @@ -3279,16 +3040,15 @@ static boolean isRegExpType(Type targetType) { static boolean isStructuredType(Type type) { Type referredType = getImpliedType(type); - switch (referredType.getTag()) { - case TypeTags.ARRAY_TAG: - case TypeTags.TUPLE_TAG: - case TypeTags.MAP_TAG: - case TypeTags.RECORD_TYPE_TAG: - case TypeTags.TABLE_TAG: - return true; - default: - return false; - } + return switch (referredType.getTag()) { + case TypeTags.ARRAY_TAG, + TypeTags.TUPLE_TAG, + TypeTags.MAP_TAG, + TypeTags.RECORD_TYPE_TAG, + TypeTags.TABLE_TAG -> + true; + default -> false; + }; } /** @@ -3307,11 +3067,9 @@ public TypePair(Type sourceType, Type targetType) { @Override public boolean equals(Object obj) { - if (!(obj instanceof TypePair)) { + if (!(obj instanceof TypePair other)) { return false; } - - TypePair other = (TypePair) obj; return this.sourceType.equals(other.sourceType) && this.targetType.equals(other.targetType); } } @@ -3329,42 +3087,6 @@ private static boolean isHandleValueRefEqual(Object lhsValue, Object rhsValue) { return lhsHandle.getValue() == rhsHandle.getValue(); } - /** - * Unordered value vector of size two, to hold two values being compared. - * - * @since 0.995.0 - */ - private static class ValuePair { - ArrayList valueList = new ArrayList<>(2); - - ValuePair(Object valueOne, Object valueTwo) { - valueList.add(valueOne); - valueList.add(valueTwo); - } - - @Override - public boolean equals(Object otherPair) { - if (!(otherPair instanceof ValuePair)) { - return false; - } - - ArrayList otherList = ((ValuePair) otherPair).valueList; - ArrayList currentList = valueList; - - if (otherList.size() != currentList.size()) { - return false; - } - - for (int i = 0; i < otherList.size(); i++) { - if (!otherList.get(i).equals(currentList.get(i))) { - return false; - } - } - - return true; - } - } - /** * Checks whether a given {@link BType} has an implicit initial value or not. * @param type {@link BType} to be analyzed. @@ -3443,7 +3165,7 @@ private static boolean checkFillerValue(BUnionType type, List unAnalyzedT private static boolean isSameBasicTypeWithFillerValue(List memberTypes) { - // here finite types and non finite types are separated + // here finite types and non-finite types are separated // for finite types only all their value space items are collected List nonFiniteTypes = new ArrayList<>(); Set combinedValueSpace = new HashSet<>(); @@ -3460,11 +3182,11 @@ private static boolean isSameBasicTypeWithFillerValue(List memberTypes) { // only finite types are there, so the check narrows to one finite type like case return hasFillerValueInValueSpace(combinedValueSpace); } else { - // non finite types are available + // non-finite types are available Iterator iterator = nonFiniteTypes.iterator(); Type firstMember = iterator.next(); - // non finite types are checked whether they are the same type + // non-finite types are checked whether they are the same type Type nextMember; while (iterator.hasNext()) { nextMember = iterator.next(); @@ -3478,8 +3200,8 @@ private static boolean isSameBasicTypeWithFillerValue(List memberTypes) { return hasFillerValue(firstMember); } - // both finite and non finite types are available - // finite types are checked whether they are the type of non finite types + // both finite and non-finite types are available + // finite types are checked whether they are the type of non-finite types if (!containsSameBasicType(firstMember, combinedValueSpace)) { return false; } @@ -3515,15 +3237,10 @@ private static boolean isIntegerSubTypeTag(int typeTag) { } private static boolean isFillerValueOfFiniteTypeBasicType(Object value) { - switch (value.toString()) { - case "0": - case "0.0": - case "false": - case "": - return true; - default: - return false; - } + return switch (value.toString()) { + case "0", "0.0", "false", "" -> true; + default -> false; + }; } private static boolean containsSameBasicType (Type nonFiniteType, Set finiteTypeValueSpace) { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/AbstractArrayValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/AbstractArrayValue.java index 80c1ac6512f0..6cbb87ab09ef 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/AbstractArrayValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/AbstractArrayValue.java @@ -33,8 +33,10 @@ import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.Map; +import java.util.Set; import static io.ballerina.runtime.api.constants.RuntimeConstants.ARRAY_LANG_LIB; +import static io.ballerina.runtime.internal.TypeChecker.isEqual; import static io.ballerina.runtime.internal.errors.ErrorCodes.INVALID_READONLY_VALUE_UPDATE; import static io.ballerina.runtime.internal.errors.ErrorReasons.INVALID_UPDATE_ERROR_IDENTIFIER; import static io.ballerina.runtime.internal.errors.ErrorReasons.getModulePrefixedReason; @@ -73,6 +75,28 @@ public void append(Object value) { add(size, value); } + @Override + public boolean equals(Object o, Set visitedValues) { + ValuePair compValuePair = new ValuePair(this, o); + for (ValuePair valuePair : visitedValues) { + if (valuePair.equals(compValuePair)) { + return true; + } + } + visitedValues.add(compValuePair); + + ArrayValue arrayValue = (ArrayValue) o; + if (arrayValue.size() != this.size()) { + return false; + } + for (int i = 0; i < this.size(); i++) { + if (!isEqual(this.get(i), arrayValue.get(i), visitedValues)) { + return false; + } + } + return true; + } + @Override public Object reverse() { throw new UnsupportedOperationException("reverse for tuple types is not supported directly."); diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValueImpl.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValueImpl.java index ef36c02af1ff..730a76819050 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValueImpl.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ArrayValueImpl.java @@ -44,7 +44,9 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.StringJoiner; @@ -1285,7 +1287,7 @@ private int getCurrentArrayLength() { @Override public int hashCode() { int result = Objects.hash(type, elementType); - result = 31 * result + Arrays.hashCode(refValues); + result = 31 * result + calculateHashCode(new ArrayList<>()); result = 31 * result + Arrays.hashCode(intValues); result = 31 * result + Arrays.hashCode(booleanValues); result = 31 * result + Arrays.hashCode(byteValues); @@ -1293,4 +1295,25 @@ public int hashCode() { result = 31 * result + Arrays.hashCode(bStringValues); return result; } + + private int calculateHashCode(List visited) { + if (refValues == null) { + return 0; + } + + int result = 1; + if (visited.contains(refValues)) { + return 31 * result + System.identityHashCode(refValues); + } + visited.add(refValues); + + for (Object ref : refValues) { + if (ref instanceof ArrayValueImpl) { + result = 31 * result + calculateHashCode(visited); + } else { + result = 31 * result + (ref == null ? 0 : ref.hashCode()); + } + } + return result; + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/CharIterator.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/CharIterator.java index fcc188ec901a..9e5c4cea6bf1 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/CharIterator.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/CharIterator.java @@ -63,5 +63,4 @@ private String getNonBmpCharWithSurrogates(long currentIndex) { public boolean hasNext() { return cursor < length; } - } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ErrorValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ErrorValue.java index f3a8683ce7a0..66733b0fe907 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ErrorValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ErrorValue.java @@ -43,12 +43,14 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.StringJoiner; import static io.ballerina.runtime.api.PredefinedTypes.TYPE_MAP; import static io.ballerina.runtime.api.constants.RuntimeConstants.BLANG_SRC_FILE_SUFFIX; import static io.ballerina.runtime.api.constants.RuntimeConstants.DOT; import static io.ballerina.runtime.api.constants.RuntimeConstants.MODULE_INIT_CLASS_NAME; +import static io.ballerina.runtime.internal.TypeChecker.isEqual; import static io.ballerina.runtime.internal.util.StringUtils.getExpressionStringVal; import static io.ballerina.runtime.internal.util.StringUtils.getStringVal; @@ -448,4 +450,19 @@ private String cleanupClassName(String className) { private boolean isCompilerAddedName(String name) { return name != null && name.startsWith("$") && name.endsWith("$"); } + + /** + * Deep equality check for error values. + * + * @param o The error value to be compared + * @param visitedValues Visited values due to circular references + * @return True if the error values are equal, false otherwise + */ + @Override + public boolean equals(Object o, Set visitedValues) { + ErrorValue errorValue = (ErrorValue) o; + return isEqual(this.getMessage(), errorValue.getMessage(), visitedValues) && + ((MapValueImpl) this.getDetails()).equals(errorValue.getDetails(), visitedValues) && + isEqual(this.getCause(), errorValue.getCause(), visitedValues); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/MapValueImpl.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/MapValueImpl.java index c1a58b87996e..d81d195cf5a7 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/MapValueImpl.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/MapValueImpl.java @@ -67,6 +67,7 @@ import static io.ballerina.runtime.api.constants.RuntimeConstants.MAP_LANG_LIB; import static io.ballerina.runtime.api.utils.TypeUtils.getImpliedType; import static io.ballerina.runtime.internal.JsonInternalUtils.mergeJson; +import static io.ballerina.runtime.internal.TypeChecker.isEqual; import static io.ballerina.runtime.internal.ValueUtils.getTypedescValue; import static io.ballerina.runtime.internal.errors.ErrorCodes.INVALID_READONLY_VALUE_UPDATE; import static io.ballerina.runtime.internal.errors.ErrorReasons.INVALID_UPDATE_ERROR_IDENTIFIER; @@ -354,30 +355,35 @@ public boolean containsKey(Object key) { } @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (o == null || getClass() != o.getClass()) { - return false; + public boolean equals(Object o, Set visitedValues) { + ValuePair compValuePair = new ValuePair(this, o); + for (ValuePair valuePair : visitedValues) { + if (valuePair.equals(compValuePair)) { + return true; + } } + visitedValues.add(compValuePair); - MapValueImpl mapValue = (MapValueImpl) o; - - if (mapValue.type.getTag() != this.type.getTag()) { + if (!(o instanceof MapValueImpl mapValue)) { return false; } - if (mapValue.referredType.getTag() != this.referredType.getTag()) { + if (this.entrySet().size() != mapValue.entrySet().size()) { return false; } - if (this.entrySet().size() != mapValue.entrySet().size()) { + if (!this.keySet().containsAll(mapValue.keySet())) { return false; } - return entrySet().equals(mapValue.entrySet()); + Iterator> mapIterator = this.entrySet().iterator(); + while (mapIterator.hasNext()) { + Map.Entry lhsMapEntry = mapIterator.next(); + if (!isEqual(lhsMapEntry.getValue(), mapValue.get(lhsMapEntry.getKey()), visitedValues)) { + return false; + } + } + return true; } /** diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RefValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RefValue.java index 7a1af9b0582a..6d90197bd231 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RefValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RefValue.java @@ -19,6 +19,8 @@ import io.ballerina.runtime.api.values.BRefValue; +import java.util.Set; + /** *

* Interface to be implemented by all the reference types. @@ -31,4 +33,7 @@ */ public interface RefValue extends SimpleValue, BRefValue { + default boolean equals(Object o, Set visitedValues) { + return o.equals(this); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpAtomQuantifier.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpAtomQuantifier.java index 5e400ffa8a6a..2ca954b78553 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpAtomQuantifier.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpAtomQuantifier.java @@ -59,7 +59,7 @@ public void setReQuantifier(RegExpQuantifier reQuantifier) { } private Object getValidReAtom(Object reAtom) { - // If reAtom is an instance of BString it's an insertion. Hence we need to parse it and check whether it's a + // If reAtom is an instance of BString it's an insertion. Hence, we need to parse it and check whether it's a // valid insertion. if (reAtom instanceof BString) { validateInsertion((BString) reAtom); diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpCapturingGroup.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpCapturingGroup.java index 80370ae581ab..0d55d18ef7a3 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpCapturingGroup.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpCapturingGroup.java @@ -30,10 +30,10 @@ * @since 2201.3.0 */ public class RegExpCapturingGroup extends RegExpCommonValue implements RegExpAtom { - private String openParen; - private RegExpFlagExpression flagExpr; - private RegExpDisjunction reDisjunction; - private String closeParen; + private final String openParen; + private final RegExpFlagExpression flagExpr; + private final RegExpDisjunction reDisjunction; + private final String closeParen; public RegExpCapturingGroup(String openParen, RegExpFlagExpression flagExpr, RegExpDisjunction reDisjunction, String closeParen) { diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpFlagExpression.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpFlagExpression.java index 87052a6b8be5..8278b55fb487 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpFlagExpression.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpFlagExpression.java @@ -30,9 +30,9 @@ * @since 2201.3.0 */ public class RegExpFlagExpression extends RegExpCommonValue { - private String questionMark; - private RegExpFlagOnOff flagsOnOff; - private String colon; + private final String questionMark; + private final RegExpFlagOnOff flagsOnOff; + private final String colon; public RegExpFlagExpression(String questionMark, RegExpFlagOnOff flagsOnOff, String colon) { this.questionMark = questionMark; diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpFlagOnOff.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpFlagOnOff.java index ba0a466ba0b2..35ae37ce4bb6 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpFlagOnOff.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpFlagOnOff.java @@ -30,7 +30,7 @@ * @since 2201.3.0 */ public class RegExpFlagOnOff extends RegExpCommonValue { - private String flags; + private final String flags; public RegExpFlagOnOff(String flags) { this.flags = flags; diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpQuantifier.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpQuantifier.java index 1f5e6ef840d2..79881805a700 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpQuantifier.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpQuantifier.java @@ -30,8 +30,8 @@ * @since 2201.3.0 */ public class RegExpQuantifier extends RegExpCommonValue { - private String quantifier; - private String nonGreedyChar; + private final String quantifier; + private final String nonGreedyChar; public RegExpQuantifier(String quantifier, String nonGreedyChar) { this.quantifier = quantifier; diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpSequence.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpSequence.java index 2992344571f5..303c6ee56336 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpSequence.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpSequence.java @@ -32,7 +32,7 @@ * @since 2201.3.0 */ public class RegExpSequence extends RegExpCommonValue { - private RegExpTerm[] termsList; + private final RegExpTerm[] termsList; public RegExpSequence(ArrayValue termsList) { this.termsList = getRegExpSeqList(termsList); diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpValue.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpValue.java index 151a98822ca0..7c8df5ed7130 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpValue.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/RegExpValue.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Objects; +import java.util.Set; import static io.ballerina.runtime.internal.ValueUtils.getTypedescValue; @@ -106,4 +107,19 @@ public void freezeDirect() { public String toString() { return this.stringValue(null); } + + /** + * Deep equality check for regular expression. + * + * @param o The regular expression on the right hand side + * @param visitedValues Visited values in order to break cyclic references. + * @return True if the regular expressions are equal, else false. + */ + @Override + public boolean equals(Object o, Set visitedValues) { + if (!(o instanceof RegExpValue rhsRegExpValue)) { + return false; + } + return this.stringValue(null).equals(rhsRegExpValue.stringValue(null)); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TableValueImpl.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TableValueImpl.java index badb37a26cf7..cb1608509d53 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TableValueImpl.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TableValueImpl.java @@ -65,6 +65,8 @@ import java.util.concurrent.ConcurrentHashMap; import static io.ballerina.runtime.api.constants.RuntimeConstants.TABLE_LANG_LIB; +import static io.ballerina.runtime.api.utils.TypeUtils.getImpliedType; +import static io.ballerina.runtime.internal.TypeChecker.isEqual; import static io.ballerina.runtime.internal.ValueUtils.getTypedescValue; import static io.ballerina.runtime.internal.errors.ErrorReasons.INHERENT_TYPE_VIOLATION_ERROR_IDENTIFIER; import static io.ballerina.runtime.internal.errors.ErrorReasons.OPERATION_NOT_SUPPORTED_ERROR; @@ -461,6 +463,47 @@ public Type getIteratorNextReturnType() { return iteratorNextReturnType; } + /** + * Check whether the given table value is equal to the current value. + * + * @param o the value to check equality with + * @param visitedValues the values that have already been visited + * @return true if the current value is equal to the given value + */ + @Override + public boolean equals(Object o, Set visitedValues) { + ValuePair compValuePair = new ValuePair(this, o); + for (ValuePair valuePair : visitedValues) { + if (valuePair.equals(compValuePair)) { + return true; + } + } + visitedValues.add(compValuePair); + + if (!(o instanceof TableValueImpl table)) { + return false; + } + if (this.size() != table.size()) { + return false; + } + + boolean isLhsKeyedTable = + ((BTableType) getImpliedType(this.getType())).getFieldNames().length > 0; + boolean isRhsKeyedTable = + ((BTableType) getImpliedType(table.getType())).getFieldNames().length > 0; + Object[] lhsTableValues = this.values().toArray(); + Object[] rhsTableValues = table.values().toArray(); + if (isLhsKeyedTable != isRhsKeyedTable) { + return false; + } + for (int i = 0; i < lhsTableValues.length; i++) { + if (!isEqual(lhsTableValues[i], rhsTableValues[i], visitedValues)) { + return false; + } + } + return true; + } + private class TableIterator implements IteratorValue { private long cursor; @@ -595,7 +638,7 @@ public V getData(K key) { return null; } for (Map.Entry entry: entryList) { - if (TypeChecker.isEqual(key, entry.getKey())) { + if (isEqual(key, entry.getKey())) { return entry.getValue(); } } @@ -647,7 +690,7 @@ public V remove(K key) { List> entryList = entries.get(hash); if (entryList != null && entryList.size() > 1) { for (Map.Entry entry: entryList) { - if (TypeChecker.isEqual(key, entry.getKey())) { + if (isEqual(key, entry.getKey())) { List valueList = values.get(hash); valueList.remove(entry.getValue()); entryList.remove(entry); @@ -679,7 +722,7 @@ public boolean containsKey(K key) { if (entries.containsKey(TableUtils.hash(key, null))) { List> entryList = entries.get(TableUtils.hash(key, null)); for (Map.Entry entry: entryList) { - if (TypeChecker.isEqual(entry.getKey(), key)) { + if (isEqual(entry.getKey(), key)) { return true; } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TupleValueImpl.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TupleValueImpl.java index 1283cf5a11cc..2fdfb69213ce 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TupleValueImpl.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/TupleValueImpl.java @@ -69,29 +69,12 @@ public class TupleValueImpl extends AbstractArrayValue { protected TupleType tupleType; protected Type type; Object[] refValues; - private int minSize; - private boolean hasRestElement; // cached value for ease of access + private final int minSize; + private final boolean hasRestElement; // cached value for ease of access private BTypedesc typedesc; private TypedescValueImpl inherentType; // ------------------------ Constructors ------------------------------------------------------------------- - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (o == null || getClass() != o.getClass()) { - return false; - } - - TupleValueImpl that = (TupleValueImpl) o; - return minSize == that.minSize && - hasRestElement == that.hasRestElement && - type.equals(that.type) && - Arrays.equals(refValues, that.refValues); - } - public TupleValueImpl(Object[] values, TupleType type) { this.refValues = values; this.type = this.tupleType = type; diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ValuePair.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ValuePair.java new file mode 100644 index 000000000000..2c7edbe3f8cb --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/ValuePair.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.runtime.internal.values; + +import java.util.HashSet; +import java.util.Set; + +/** + * Unordered value vector of size two, to hold two values being compared. + * + * @since 2201.9.0 + */ + +public class ValuePair { + + Set valuePairSet = new HashSet<>(2); + + public ValuePair(Object obj1, Object obj2) { + valuePairSet.add(obj1); + valuePairSet.add(obj2); + } + + public boolean equals(Object o) { + if (!(o instanceof ValuePair valuePair)) { + return false; + } + Set otherSet = valuePair.valuePairSet; + Set currentSet = this.valuePairSet; + for (Object otherObj : otherSet) { + if (!currentSet.contains(otherObj)) { + return false; + } + } + return true; + } +} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlComment.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlComment.java index 71e346c5cecf..24725ac7bca8 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlComment.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlComment.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.Set; /** * XML nodes containing comment data. @@ -33,7 +34,7 @@ */ public class XmlComment extends XmlNonElementItem { - private String data; + private final String data; public XmlComment(String data) { this.data = data; @@ -119,4 +120,19 @@ public int hashCode() { public boolean equals(Object obj) { return this == obj; } + + /** + * Deep equality check for xml comment. + * + * @param o The xml comment on the right hand side + * @param visitedValues Visited values in order to break cyclic references. + * @return True if the xml comments are equal, else false. + */ + @Override + public boolean equals(Object o, Set visitedValues) { + if (!(o instanceof XmlComment rhXMLComment)) { + return false; + } + return this.getTextValue().equals(rhXMLComment.getTextValue()); + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlItem.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlItem.java index ab15068c0347..72b91671e195 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlItem.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlItem.java @@ -48,6 +48,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.Set; import javax.xml.XMLConstants; import javax.xml.namespace.QName; @@ -57,6 +58,7 @@ import static io.ballerina.runtime.api.constants.RuntimeConstants.XML_LANG_LIB; import static io.ballerina.runtime.api.types.XmlNodeType.ELEMENT; import static io.ballerina.runtime.api.types.XmlNodeType.TEXT; +import static io.ballerina.runtime.internal.TypeChecker.isEqual; /** * {@code XMLItem} represents a single XML element in Ballerina. @@ -683,6 +685,31 @@ public int hashCode() { return Objects.hash(name, children, attributes, probableParents); } + /** + * Deep equality check for XML Item. + * + * @param o The XML Item to be compared + * @param visitedValues Visited values due to circular references + * @return True if the XML Items are equal; False otherwise + */ + @Override + public boolean equals(Object o, Set visitedValues) { + if (o instanceof XmlItem rhsXMLItem) { + if (!(rhsXMLItem.getQName().equals(this.getQName()))) { + return false; + } + if (!(rhsXMLItem.getAttributesMap().entrySet().equals(this.getAttributesMap().entrySet()))) { + return false; + } + return isEqual(rhsXMLItem.getChildrenSeq(), this.getChildrenSeq()); + } + if (o instanceof XmlSequence rhsXMLSequence) { + return rhsXMLSequence.getChildrenList().size() == 1 && + isEqual(this, rhsXMLSequence.getChildrenList().get(0)); + } + return false; + } + private interface SetAttributeFunction { void set(String localName, String namespace, String prefix, String value); } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlPi.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlPi.java index 1f50617f219f..764b4e88d460 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlPi.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlPi.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.Set; /** * XML nodes containing processing instructions. @@ -118,6 +119,21 @@ public boolean equals(Object obj) { return this == obj; } + /** + * Deep equality check for XML Processing Instruction. + * + * @param o The XML on the right hand side + * @param visitedValues Visited values in order to break cyclic references. + * @return True if the XML values are equal, else false. + */ + @Override + public boolean equals(Object o, Set visitedValues) { + if (!(o instanceof XmlPi rhsXMLPi)) { + return false; + } + return this.getData().equals(rhsXMLPi.getData()) && this.getTarget().equals(rhsXMLPi.getTarget()); + } + @Override public int hashCode() { return Objects.hash(data, target); diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlSequence.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlSequence.java index b41eafe5d225..1fa8c0dffdc7 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlSequence.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlSequence.java @@ -23,6 +23,7 @@ import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.types.XmlNodeType; import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BLink; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; @@ -45,6 +46,7 @@ import static io.ballerina.runtime.api.constants.RuntimeConstants.STRING_EMPTY_VALUE; import static io.ballerina.runtime.api.constants.RuntimeConstants.XML_LANG_LIB; +import static io.ballerina.runtime.internal.TypeChecker.isEqual; /** *

@@ -654,4 +656,35 @@ public Type getIteratorNextReturnType() { } return iteratorNextReturnType; } + + /** + * Deep equality check for XML Sequence. + * + * @param o The XML Sequence to be compared + * @param visitedValues Visited values in previous recursive calls + * @return True if the XML Sequences are equal; False otherwise + */ + @Override + public boolean equals(Object o, Set visitedValues) { + if (o instanceof XmlSequence rhsXMLSequence) { + return isXMLSequenceChildrenEqual(this.getChildrenList(), rhsXMLSequence.getChildrenList()); + } + if (o instanceof XmlItem) { + return this.getChildrenList().size() == 1 && + isEqual(this.getChildrenList().get(0), o); + } + return this.getChildrenList().isEmpty() && TypeUtils.getType(o) == PredefinedTypes.TYPE_XML_NEVER; + } + + private static boolean isXMLSequenceChildrenEqual(List lhsList, List rhsList) { + if (lhsList.size() != rhsList.size()) { + return false; + } + for (int i = 0; i < lhsList.size(); i++) { + if (!isEqual(lhsList.get(i), rhsList.get(i))) { + return false; + } + } + return true; + } } diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlText.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlText.java index 0f516fb0965c..396d9807abb0 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlText.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/values/XmlText.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.Set; /** * XML nodes containing atomic content such as text, comment and processing instructions. @@ -117,6 +118,22 @@ public boolean equals(Object obj) { return this == obj; } + /** + * Deep equality check for XML Text. + * + * @param o The XML Text to be compared + * @param visitedValues Visited values in previous recursive calls + * @return True if the XML Texts are equal; False otherwise + */ + @Override + public boolean equals(Object o, Set visitedValues) { + if (o instanceof XmlText rhsXMLText) { + return this.getTextValue().equals(rhsXMLText.getTextValue()); + } + return this.getType() == PredefinedTypes.TYPE_XML_NEVER && (o instanceof XmlSequence) && + ((XmlSequence) o).getChildrenList().isEmpty(); + } + @Override public int hashCode() { return Objects.hash(data); diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/binaryoperations/EqualAndNotEqualOperationsTest.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/binaryoperations/EqualAndNotEqualOperationsTest.java index 1a262dff5c35..4f145721dc4e 100644 --- a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/binaryoperations/EqualAndNotEqualOperationsTest.java +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/binaryoperations/EqualAndNotEqualOperationsTest.java @@ -60,7 +60,7 @@ public Object[] getValueTestFunctions() { "checkDecimalEquality", "checkStringEquality", "checkEqualityToNil", "checkAnyDataEquality", "testIntByteEqualityPositive", "testIntByteEqualityNegative", "testIntersectingUnionEquality", "testTableEquality", "testEqualityWithNonAnydataType", "testEqualityByteWithIntSubTypes", - "checkFiniteTypeEquality" + "checkFiniteTypeEquality", "testEqualityWithCyclicReferences" }; } diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/expressions/binaryoperations/equal_and_not_equal_operation.bal b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/binaryoperations/equal_and_not_equal_operation.bal index 10ec84ff2ec7..ae0be91693b8 100644 --- a/tests/jballerina-unit-test/src/test/resources/test-src/expressions/binaryoperations/equal_and_not_equal_operation.bal +++ b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/binaryoperations/equal_and_not_equal_operation.bal @@ -1779,3 +1779,44 @@ function testEqualityByteWithIntSubTypes() { (g !== a) || (h !== a)); // Need to add (a !== f) , (f !== a) after fixing #32924 } + +type Part anydata[]; +type J anydata; + +function testEqualityWithCyclicReferences() { + map m1 = {one: 1, two: 2}; + map m2 = {one: 1, two: 2}; + m1["three"] = m2; + m2["three"] = m1; + test:assertTrue(m1 == m2); + test:assertFalse(m1 != m2); + + map j1 = { loop: () }; + map j2 = { loop: () }; + j1["loop"] = j1; + j2["loop"] = j1; + map j3 = { loop: () }; + j3["loop"] = { loop: { loop: { loop: j3 }}}; + test:assertTrue(j1 == j3); + + Part yin = []; + Part yang = []; + yin[0] = yang; + yang[0] = yin; + test:assertTrue(yin == yang); + + table> t1 = table []; + table> t2 = table []; + t1.add({loop: t2}); + t2.add({loop: t1}); + test:assertTrue(t1 == t2); + + table> t3 = table []; + table> t4 = table []; + table> t5 = table []; + t3.add({loop: t4}); + t4.add({loop: t5}); + t5.add({loop: t3}); + test:assertTrue(t3 == t4); + test:assertTrue(t3 == t5); +}