Skip to content

Commit

Permalink
Implement structural equality for structs
Browse files Browse the repository at this point in the history
RELNOTES: Structs in Skylark are tested for structural equality instead of reference equality.

--
MOS_MIGRATED_REVID=139583726
  • Loading branch information
vladmos authored and dslomov committed Nov 21, 2016
1 parent b4c4bce commit fdfa988
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package com.google.devtools.build.lib.packages;

import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Ordering;
Expand All @@ -31,6 +32,9 @@
import com.google.devtools.build.lib.syntax.SkylarkType;
import com.google.devtools.build.lib.util.Preconditions;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;

Expand Down Expand Up @@ -175,6 +179,10 @@ public String errorMessage(String name) {

@Override
public boolean isImmutable() {
// If the constructor is not yet exported the hash code of the object is subject to change
if (!constructor.isExported()) {
return false;
}
for (Object item : values.values()) {
if (!EvalUtils.isImmutable(item)) {
return false;
Expand All @@ -183,6 +191,43 @@ public boolean isImmutable() {
return true;
}

@Override
public boolean equals(Object otherObject) {
if (!(otherObject instanceof SkylarkClassObject)) {
return false;
}
SkylarkClassObject other = (SkylarkClassObject) otherObject;
if (this == other) {
return true;
}
if (!this.constructor.equals(other.constructor)) {
return false;
}
// Compare objects' keys and values
if (!this.getKeys().equals(other.getKeys())) {
return false;
}
for (String key : getKeys()) {
if (!this.getValue(key).equals(other.getValue(key))) {
return false;
}
}
return true;
}

@Override
public int hashCode() {
List<String> keys = new ArrayList<>(getKeys());
Collections.sort(keys);
List<Object> objectsToHash = new ArrayList<>();
objectsToHash.add(constructor);
for (String key : keys) {
objectsToHash.add(key);
objectsToHash.add(getValue(key));
}
return Objects.hashCode(objectsToHash.toArray());
}

/**
* Convert the object to string using Skylark syntax. The output tries to be
* reversible (but there is no guarantee, it depends on the actual values).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,31 @@ public void export(Label extensionLabel, String exportedName) {

@Override
public int hashCode() {
if (isExported()) {
return getKey().hashCode();
}
return System.identityHashCode(this);
}

@Override
public boolean equals(@Nullable Object other) {
return other == this;
public boolean equals(@Nullable Object otherObject) {
if (!(otherObject instanceof SkylarkClassObjectConstructor)) {
return false;
}
SkylarkClassObjectConstructor other = (SkylarkClassObjectConstructor) otherObject;

if (this.isExported() && other.isExported()) {
return this.getKey().equals(other.getKey());
} else {
return this == other;
}
}

@Override
public boolean isImmutable() {
// Hash code for non exported constructors may be changed
return isExported();
}

/**
* A representation of {@link SkylarkClassObjectConstructor}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,26 @@ public void testStructFields() throws Exception {
assertEquals(2, x.getValue("b"));
}

@Test
public void testStructEquality() throws Exception {
assertTrue((Boolean) eval("struct(a = 1, b = 2) == struct(b = 2, a = 1)"));
assertFalse((Boolean) eval("struct(a = 1) == struct(a = 1, b = 2)"));
assertFalse((Boolean) eval("struct(a = 1, b = 2) == struct(a = 1)"));
// Compare a recursive object to itself to make sure reference equality is checked
assertTrue((Boolean) eval("s = (struct(a = 1, b = [])); s.b.append(s); s == s"));
assertFalse((Boolean) eval("struct(a = 1, b = 2) == struct(a = 1, b = 3)"));
assertFalse((Boolean) eval("struct(a = 1) == [1]"));
assertFalse((Boolean) eval("[1] == struct(a = 1)"));
assertTrue((Boolean) eval("struct() == struct()"));
assertFalse((Boolean) eval("struct() == struct(a = 1)"));

eval("foo = provider(); bar = provider()");
assertFalse((Boolean) eval("struct(a = 1) == foo(a = 1)"));
assertFalse((Boolean) eval("foo(a = 1) == struct(a = 1)"));
assertFalse((Boolean) eval("foo(a = 1) == bar(a = 1)"));
assertTrue((Boolean) eval("foo(a = 1) == foo(a = 1)"));
}

@Test
public void testStructAccessingFieldsFromSkylark() throws Exception {
eval("x = struct(a = 1, b = 2)", "x1 = x.a", "x2 = x.b");
Expand Down

0 comments on commit fdfa988

Please sign in to comment.