Skylark: implemented reversed()

--
MOS_MIGRATED_REVID=110141376
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 3d8c7fe..f43f714 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
@@ -20,6 +20,7 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
+import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.events.Event;
 import com.google.devtools.build.lib.events.Location;
@@ -935,6 +936,40 @@
       };
 
   @SkylarkSignature(
+    name = "reversed",
+    returnType = MutableList.class,
+    doc = "Returns a list that contains the elements of the original sequence in reversed order.",
+    mandatoryPositionals = {
+      @Param(
+        name = "sequence",
+        type = Object.class,
+        doc = "The sequence to be reversed (string, list or tuple)."
+      )
+    },
+    useLocation = true,
+    useEnvironment = true
+  )
+  private static BuiltinFunction reversed =
+      new BuiltinFunction("reversed") {
+        @SuppressWarnings("unused") // Accessed via Reflection.
+        public MutableList invoke(Object sequence, Location loc, Environment env)
+            throws EvalException {
+          // We only allow lists and strings.
+          if (sequence instanceof Map) {
+            throw new EvalException(
+                loc, "Argument to reversed() must be a sequence, not a dictionary.");
+          } else if (sequence instanceof NestedSet || sequence instanceof SkylarkNestedSet) {
+            throw new EvalException(loc, "Argument to reversed() must be a sequence, not a set.");
+          }
+          LinkedList<Object> tmpList = new LinkedList<>();
+          for (Object element : EvalUtils.toIterable(sequence, loc)) {
+            tmpList.addFirst(element);
+          }
+          return new MutableList(tmpList, env);
+        }
+      };
+
+  @SkylarkSignature(
     name = "append",
     objectType = MutableList.class,
     returnType = Runtime.NoneType.class,
@@ -1683,8 +1718,10 @@
       + "</pre>")
   static final class DictModule {}
 
-  static final List<BaseFunction> buildGlobalFunctions = ImmutableList.<BaseFunction>of(
-      bool, dict, enumerate, int_, len, list, minus, range, repr, select, sorted, str, zip);
+  static final List<BaseFunction> buildGlobalFunctions =
+      ImmutableList.<BaseFunction>of(
+          bool, dict, enumerate, int_, len, list, minus, range, repr, reversed, select, sorted, str,
+          zip);
 
   static final List<BaseFunction> skylarkGlobalFunctions =
       ImmutableList.<BaseFunction>builder()
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EnvironmentTest.java b/src/test/java/com/google/devtools/build/lib/syntax/EnvironmentTest.java
index b689fbd..3e18319 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/EnvironmentTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/EnvironmentTest.java
@@ -123,12 +123,12 @@
     assertEquals(Sets.newHashSet("foo", "wiz",
             "False", "None", "True",
             "-", "bool", "dict", "enumerate", "int", "len", "list",
-            "range", "repr", "select", "sorted", "str", "zip"),
+            "range", "repr", "reversed", "select", "sorted", "str", "zip"),
         outerEnv.getVariableNames());
     assertEquals(Sets.newHashSet("foo", "wiz", "quux",
             "False", "None", "True",
             "-", "bool", "dict", "enumerate", "int", "len", "list",
-            "range", "repr", "select", "sorted", "str", "zip"),
+            "range", "repr", "reversed", "select", "sorted", "str", "zip"),
         innerEnv.getVariableNames());
   }
 
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 e76471c..d1e307a 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
@@ -770,6 +770,51 @@
   }
 
   @Test
+  public void testReversedWithInvalidTypes() throws Exception {
+    new BothModesTest()
+        .testIfExactError("type 'NoneType' is not iterable", "reversed(None)")
+        .testIfExactError("type 'int' is not iterable", "reversed(1)")
+        .testIfExactError(
+            "Argument to reversed() must be a sequence, not a dictionary.", "reversed({1: 3})");
+    new SkylarkTest()
+        .testIfExactError(
+            "Argument to reversed() must be a sequence, not a set.", "reversed(set([1]))");
+  }
+
+  @Test
+  public void testReversedWithLists() throws Exception {
+    new BothModesTest()
+        .testEval("reversed([])", "[]")
+        .testEval("reversed([1])", "[1]")
+        .testEval("reversed([1, 2, 3, 4, 5])", "[5, 4, 3, 2, 1]")
+        .testEval("reversed([[1, 2], 3, 4, [5]])", "[[5], 4, 3, [1, 2]]")
+        .testEval("reversed([1, 1, 1, 1, 2])", "[2, 1, 1, 1, 1]");
+  }
+
+  @Test
+  public void testReversedWithStrings() throws Exception {
+    new BothModesTest()
+        .testEval("reversed('')", "['']")
+        .testEval("reversed('a')", "['a']")
+        .testEval("reversed('abc')", "['c', 'b', 'a']")
+        .testEval("reversed('__test  ')", "[' ', ' ', 't', 's', 'e', 't', '_', '_']")
+        .testEval("reversed('bbb')", "['b', 'b', 'b']");
+  }
+
+  @Test
+  public void testReversedNoSideEffects() throws Exception {
+    new SkylarkTest()
+        .testEval(
+            "def foo():\n"
+                + "  x = ['a', 'b']\n"
+                + "  y = reversed(x)\n"
+                + "  y += ['c']\n"
+                + "  return x\n"
+                + "foo()",
+            "['a', 'b']");
+  }
+
+  @Test
   public void testListSlice() throws Exception {
     new BothModesTest()
         .testEval("[0,1,2,3][0:-1]", "[0, 1, 2]")