Implemented Python's dict() in Skylark

--
MOS_MIGRATED_REVID=99852261
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
index fb59cda..5fbaaa9 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
@@ -31,6 +31,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -976,6 +977,48 @@
       }
     }
   };
+  
+  @SkylarkSignature(name = "dict", returnType = Map.class,
+      doc =
+      "Creates a <a href=\"#modules.dict\">dictionary</a> from an optional positional "
+      + "argument and an optional set of keyword arguments. Values from the keyword argument "
+      + "will overwrite values from the positional argument if a key appears multiple times. "
+      + "Dictionaries are always sorted by their keys",
+      optionalPositionals = {
+          @Param(name = "args", type = Iterable.class, defaultValue = "[]",
+              doc =
+              "List of entries. Entries must be tuples or lists with exactly "
+              + "two elements: key, value"),
+      },
+      extraKeywords = {@Param(name = "kwargs", doc = "Dictionary of additional entries.")},
+      useLocation = true)
+  private static final BuiltinFunction dict = new BuiltinFunction("dict") {
+    @SuppressWarnings("unused")
+    public Map<Object, Object> invoke(Iterable<Object> args, Map<Object, Object> kwargs,
+        Location loc) throws EvalException, ConversionException {
+      try {
+        Map<Object, Object> result = new HashMap<>();
+        List<Object> list = Type.OBJECT_LIST.convert(args, "dict(args)");
+
+        for (Object tuple : list) {
+          List<Object> mapping = Type.OBJECT_LIST.convert(tuple, "dict(args)");
+          int numElements = mapping.size();
+
+          if (numElements != 2) {
+            throw new EvalException(
+                location,
+                String.format(
+                    "Tuple has length %d, but exactly two elements are required", numElements));
+          }
+          result.put(mapping.get(0), mapping.get(1));
+        }
+        result.putAll(kwargs);
+        return result;
+      } catch (IllegalArgumentException | ClassCastException | NullPointerException ex) {
+        throw new EvalException(loc, ex);
+      }
+    }
+  };
 
   @SkylarkSignature(name = "union", objectType = SkylarkNestedSet.class,
       returnType = SkylarkNestedSet.class,
@@ -1328,7 +1371,7 @@
   private static final List<BaseFunction> skylarkGlobalFunctions =
       ImmutableList.<BaseFunction>builder()
       .addAll(pureGlobalFunctions)
-      .add(list, struct, hasattr, getattr, set, dir, type, fail, print, zip)
+      .add(list, struct, hasattr, getattr, set, dict, dir, type, fail, print, zip)
       .build();
 
   /**
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/ValidationTests.java b/src/test/java/com/google/devtools/build/lib/syntax/ValidationTests.java
index 3326fec..57b341f 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/ValidationTests.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/ValidationTests.java
@@ -180,11 +180,11 @@
 
   @Test
   public void testFuncReturningDictAssignmentAsLValue() throws Exception {
-    checkError("can only assign to variables and tuples, not to 'dict()['b']'",
-        "def dict():",
+    checkError("can only assign to variables and tuples, not to 'my_dict()['b']'",
+        "def my_dict():",
         "  return {'a': 1}",
         "def func():",
-        "  dict()['b'] = 2",
+        "  my_dict()['b'] = 2",
         "  return d\n");
   }