Allow dicts to contain non-comparable objects as keys

RELNOTES: Skylark dicts internally don't rely on keys order anymore and accept
any hashable values (i.e. structs with immutable values) as keys. Iteration order of dictionaries is no longer specified.

--
PiperOrigin-RevId: 141055080
MOS_MIGRATED_REVID=141055080
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
index 17f5484..066c544 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
@@ -657,6 +657,11 @@
   }
 
   @Test
+  public void testProtoFieldsOrder() throws Exception {
+    checkTextMessage("struct(d=4, b=2, c=3, a=1).to_proto()", "a: 1", "b: 2", "c: 3", "d: 4");
+  }
+
+  @Test
   public void testTextMessageEscapes() throws Exception {
     checkTextMessage("struct(name='a\"b').to_proto()", "name: \"a\\\"b\"");
     checkTextMessage("struct(name='a\\'b').to_proto()", "name: \"a'b\"");
@@ -810,6 +815,14 @@
   }
 
   @Test
+  public void testStructIncomparability() throws Exception {
+    checkErrorContains("Cannot compare structs", "struct(a = 1) < struct(a = 2)");
+    checkErrorContains("Cannot compare structs", "struct(a = 1) > struct(a = 2)");
+    checkErrorContains("Cannot compare structs", "struct(a = 1) <= struct(a = 2)");
+    checkErrorContains("Cannot compare structs", "struct(a = 1) >= struct(a = 2)");
+  }
+
+  @Test
   public void testStructAccessingFieldsFromSkylark() throws Exception {
     eval("x = struct(a = 1, b = 2)", "x1 = x.a", "x2 = x.b");
     assertThat(lookup("x1")).isEqualTo(1);
@@ -934,6 +947,18 @@
   }
 
   @Test
+  public void testStructsInDicts() throws Exception {
+    eval("d = {struct(a = 1): 'aa', struct(b = 2): 'bb'}");
+    assertThat(eval("d[struct(a = 1)]")).isEqualTo("aa");
+    assertThat(eval("d[struct(b = 2)]")).isEqualTo("bb");
+    assertThat(eval("str([d[k] for k in d])")).isEqualTo("[\"aa\", \"bb\"]");
+
+    checkErrorContains(
+        "unhashable type: 'struct'",
+        "{struct(a = []): 'foo'}");
+  }
+
+  @Test
   public void testStructMembersAreImmutable() throws Exception {
     checkErrorContains(
         "cannot assign to 's.x'",
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java
index b60dace..a36fc74 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleContextTest.java
@@ -547,27 +547,27 @@
     assertEquals(
         SkylarkList.createImmutable(
             ImmutableList.<String>of(
-                "cmd",
-                "compatible_with",
-                "executable",
-                "features",
+                "name",
+                "visibility",
+                "tags",
+                "generator_name",
                 "generator_function",
                 "generator_location",
-                "generator_name",
-                "heuristic_label_expansion",
-                "kind",
-                "local",
-                "message",
-                "name",
-                "output_to_bindir",
-                "outs",
+                "features",
+                "compatible_with",
                 "restricted_to",
                 "srcs",
-                "stamp",
-                "tags",
-                "toolchains",
                 "tools",
-                "visibility")),
+                "toolchains",
+                "outs",
+                "cmd",
+                "output_to_bindir",
+                "local",
+                "message",
+                "executable",
+                "stamp",
+                "heuristic_label_expansion",
+                "kind")),
         result);
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
index fa72014..9bdaa15 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
@@ -146,7 +146,11 @@
         .testStatement("'hello' == 'bye'", false)
         .testStatement("None == None", true)
         .testStatement("[1, 2] == [1, 2]", true)
-        .testStatement("[1, 2] == [2, 1]", false);
+        .testStatement("[1, 2] == [2, 1]", false)
+        .testStatement("{'a': 1, 'b': 2} == {'b': 2, 'a': 1}", true)
+        .testStatement("{'a': 1, 'b': 2} == {'a': 1}", false)
+        .testStatement("{'a': 1, 'b': 2} == {'a': 1, 'b': 2, 'c': 3}", false)
+        .testStatement("{'a': 1, 'b': 2} == {'a': 1, 'b': 3}", false);
   }
 
   @Test
@@ -157,7 +161,11 @@
         .testStatement("'hello' != 'hel' + 'lo'", false)
         .testStatement("'hello' != 'bye'", true)
         .testStatement("[1, 2] != [1, 2]", false)
-        .testStatement("[1, 2] != [2, 1]", true);
+        .testStatement("[1, 2] != [2, 1]", true)
+        .testStatement("{'a': 1, 'b': 2} != {'b': 2, 'a': 1}", false)
+        .testStatement("{'a': 1, 'b': 2} != {'a': 1}", true)
+        .testStatement("{'a': 1, 'b': 2} != {'a': 1, 'b': 2, 'c': 3}", true)
+        .testStatement("{'a': 1, 'b': 2} != {'a': 1, 'b': 3}", true);
   }
 
   @Test
@@ -272,7 +280,10 @@
         .update(kwargs.getName(), kwargs)
         .testEval(
             "kwargs(foo=1, bar='bar', wiz=[1,2,3]).items()",
-            "[('bar', 'bar'), ('foo', 1), ('wiz', [1, 2, 3])]");
+            "[('foo', 1), ('bar', 'bar'), ('wiz', [1, 2, 3])]")
+        .testEval(
+            "kwargs(wiz=[1,2,3], bar='bar', foo=1).items()",
+            "[('wiz', [1, 2, 3]), ('bar', 'bar'), ('foo', 1)]");
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
index ea81adb..a001f3f 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
@@ -1311,8 +1311,8 @@
     new BothModesTest()
         .testEval("{1: 'foo'}.values()", "['foo']")
         .testEval("{}.values()", "[]")
-        .testEval("{True: 3, False: 5}.values()", "[5, 3]")
-        .testEval("{'a': 5, 'c': 2, 'b': 4, 'd': 3}.values()", "[5, 4, 2, 3]");
+        .testEval("{True: 3, False: 5}.values()", "[3, 5]")
+        .testEval("{'a': 5, 'c': 2, 'b': 4, 'd': 3}.values()", "[5, 2, 4, 3]");
     // sorted by keys
   }
 
@@ -1321,9 +1321,9 @@
     new BothModesTest()
         .testEval("{1: 'foo'}.keys()", "[1]")
         .testEval("{}.keys()", "[]")
-        .testEval("{True: 3, False: 5}.keys()", "[False, True]")
+        .testEval("{True: 3, False: 5}.keys()", "[True, False]")
         .testEval(
-            "{1:'a', 2:'b', 6:'c', 0:'d', 5:'e', 4:'f', 3:'g'}.keys()", "[0, 1, 2, 3, 4, 5, 6]");
+            "{1:'a', 2:'b', 6:'c', 0:'d', 5:'e', 4:'f', 3:'g'}.keys()", "[1, 2, 6, 0, 5, 4, 3]");
   }
 
   @Test
@@ -1342,7 +1342,7 @@
         .testEval("{'a': 'foo'}.items()", "[('a', 'foo')]")
         .testEval("{}.items()", "[]")
         .testEval("{1: 3, 2: 5}.items()", "[(1, 3), (2, 5)]")
-        .testEval("{'a': 5, 'c': 2, 'b': 4}.items()", "[('a', 5), ('b', 4), ('c', 2)]");
+        .testEval("{'a': 5, 'c': 2, 'b': 4}.items()", "[('a', 5), ('c', 2), ('b', 4)]");
   }
 
   @Test
@@ -1378,9 +1378,9 @@
             "popitem(): dictionary is empty",
             "d = {2: 'bar', 3: 'baz', 1: 'foo'}\n"
                 + "if len(d) != 3: fail('popitem 0')\n"
-                + "if d.popitem() != (1, 'foo'): fail('popitem 1')\n"
                 + "if d.popitem() != (2, 'bar'): fail('popitem 2')\n"
                 + "if d.popitem() != (3, 'baz'): fail('popitem 3')\n"
+                + "if d.popitem() != (1, 'foo'): fail('popitem 1')\n"
                 + "if d != {}: fail('popitem 4')\n"
                 + "d.popitem()");
   }
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
index dcb927f..e949290 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
@@ -919,7 +919,7 @@
         "  for a in d:",
         "    s += a",
         "  return s",
-        "s = foo()").testLookup("s", "abc");
+        "s = foo()").testLookup("s", "cab");
   }
 
   @Test