Add unary plus operator, +int

Implements: https://github.com/bazelbuild/starlark/issues/20#issuecomment-456647994

Closes #8890.

PiperOrigin-RevId: 258388687
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
index 13cc21c..dfb82d6 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
@@ -673,6 +673,13 @@
           UnaryOperatorExpression minus = new UnaryOperatorExpression(TokenKind.MINUS, expr);
           return setLocation(minus, start, expr);
         }
+      case PLUS:
+        {
+          nextToken();
+          Expression expr = parsePrimaryWithSuffix();
+          UnaryOperatorExpression plus = new UnaryOperatorExpression(TokenKind.PLUS, expr);
+          return setLocation(plus, start, expr);
+        }
       default:
         {
           syntaxError("expected expression");
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/UnaryOperatorExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/UnaryOperatorExpression.java
index 7341708..b16a07f 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/UnaryOperatorExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/UnaryOperatorExpression.java
@@ -19,7 +19,7 @@
 /** A UnaryOperatorExpression represents a unary operator expression, 'op x'. */
 public final class UnaryOperatorExpression extends Expression {
 
-  private final TokenKind op; // NOT or MINUS
+  private final TokenKind op; // NOT, MINUS or PLUS
   private final Expression x;
 
   public UnaryOperatorExpression(TokenKind op, Expression x) {
@@ -75,6 +75,14 @@
           // Fails for -MIN_INT.
           throw new EvalException(loc, e.getMessage());
         }
+      case PLUS:
+        if (!(value instanceof Integer)) {
+          throw new EvalException(
+              loc,
+              String.format(
+                  "unsupported operand type for +: '%s'", EvalUtils.getDataTypeName(value)));
+        }
+        return value;
 
       default:
         throw new AssertionError("Unsupported unary operator: " + op);
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/BuildFileASTTest.java b/src/test/java/com/google/devtools/build/lib/syntax/BuildFileASTTest.java
index e72f01b..9e2d211 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/BuildFileASTTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/BuildFileASTTest.java
@@ -137,7 +137,7 @@
         "           srcs = libs,",
         "           includes = [ abi + opt_level + '/include' ])");
     assertThat(buildFile.containsErrors()).isTrue();
-    assertContainsError("syntax error at '+': expected expression");
+    assertContainsError("syntax error at '*': expected expression");
     assertThat(buildFile.exec(env, getEventHandler())).isFalse();
     MoreAsserts.assertDoesNotContainEvent(getEventCollector(), "$error$");
     // This message should not be printed anymore.
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java b/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java
index cfc9641..bc4d8cc 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java
@@ -838,22 +838,23 @@
   @Test
   public void testParserRecovery() throws Exception {
     setFailFast(false);
-    List<Statement> statements = parseFileForSkylark(
-        "def foo():",
-        "  a = 2 for 4",  // parse error
-        "  b = [3, 4]",
-        "",
-        "d = 4 ada",  // parse error
-        "",
-        "def bar():",
-        "  a = [3, 4]",
-        "  b = 2 + + 5",  // parse error
-        "");
+    List<Statement> statements =
+        parseFileForSkylark(
+            "def foo():",
+            "  a = 2 for 4", // parse error
+            "  b = [3, 4]",
+            "",
+            "d = 4 ada", // parse error
+            "",
+            "def bar():",
+            "  a = [3, 4]",
+            "  b = 2 * * 5", // parse error
+            "");
 
     assertThat(getEventCollector()).hasSize(3);
     assertContainsError("syntax error at 'for': expected newline");
     assertContainsError("syntax error at 'ada': expected newline");
-    assertContainsError("syntax error at '+': expected expression");
+    assertContainsError("syntax error at '*': expected expression");
     assertThat(statements).hasSize(3);
   }
 
@@ -872,8 +873,8 @@
   @Test
   public void testParserContainsErrors() throws Exception {
     setFailFast(false);
-    parseFile("+");
-    assertContainsError("syntax error at '+'");
+    parseFile("*");
+    assertContainsError("syntax error at '*'");
   }
 
   @Test
diff --git a/src/test/starlark/testdata/int.sky b/src/test/starlark/testdata/int.sky
index 3bc2105..91212da 100644
--- a/src/test/starlark/testdata/int.sky
+++ b/src/test/starlark/testdata/int.sky
@@ -60,6 +60,15 @@
 
 compound()
 
+
+# unary operators
+
+assert_eq(+4, 4)
+assert_eq(-4, -4)
+assert_eq(++4, 4)
+assert_eq(+-4, -4)
+assert_eq(-+-4, 4)
+
 ---
 1 // 0  ### integer division by zero
 ---