Introduce '|' operator for set union.

--
MOS_MIGRATED_REVID=101363350
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
index 65ea8df..9d489e0 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
@@ -121,18 +121,22 @@
     Object rval = rhs.eval(env);
 
     switch (operator) {
-      case PLUS: {
+      case PLUS:
         return plus(lval, rval);
-      }
 
-      case MINUS: {
+      case PIPE:
+        if (lval instanceof SkylarkNestedSet) {
+          return new SkylarkNestedSet((SkylarkNestedSet) lval, rval, getLocation());
+        }
+        break;
+
+      case MINUS:
         if (lval instanceof Integer && rval instanceof Integer) {
           return ((Integer) lval).intValue() - ((Integer) rval).intValue();
         }
         break;
-      }
 
-      case MULT: {
+      case MULT:
         // int * int
         if (lval instanceof Integer && rval instanceof Integer) {
           return ((Integer) lval).intValue() * ((Integer) rval).intValue();
@@ -148,9 +152,8 @@
           return Strings.repeat((String) rval, ((Integer) lval).intValue());
         }
         break;
-      }
 
-      case DIVIDE: {
+      case DIVIDE:
         // int / int
         if (lval instanceof Integer && rval instanceof Integer) {
           if (rval.equals(0)) {
@@ -163,9 +166,9 @@
           // We want to follow Python semantics, so we use float division and round down.
           return (int) Math.floor(new Double((Integer) lval) / (Integer) rval);
         }
-      }
+        break;
 
-      case PERCENT: {
+      case PERCENT:
         // int % int
         if (lval instanceof Integer && rval instanceof Integer) {
           if (rval.equals(0)) {
@@ -207,43 +210,33 @@
           }
         }
         break;
-      }
 
-      case EQUALS_EQUALS: {
+      case EQUALS_EQUALS:
         return lval.equals(rval);
-      }
 
-      case NOT_EQUALS: {
+      case NOT_EQUALS:
         return !lval.equals(rval);
-      }
 
-      case LESS: {
+      case LESS:
         return compare(lval, rval) < 0;
-      }
 
-      case LESS_EQUALS: {
+      case LESS_EQUALS:
         return compare(lval, rval) <= 0;
-      }
 
-      case GREATER: {
+      case GREATER:
         return compare(lval, rval) > 0;
-      }
 
-      case GREATER_EQUALS: {
+      case GREATER_EQUALS:
         return compare(lval, rval) >= 0;
-      }
 
-      case IN: {
+      case IN:
         return evalIn(lval, rval);
-      }
 
-      case NOT_IN: {
+      case NOT_IN:
         return !evalIn(lval, rval);
-      }
 
-      default: {
+      default:
         throw new AssertionError("Unsupported binary operator: " + operator);
-      }
     } // endswitch
 
     // NB: this message format is identical to that used by CPython 2.7.6 or 3.4.0,
@@ -310,6 +303,7 @@
           getLocation());
     }
 
+    // TODO(bazel-team): Remove this case. Union of sets should use '|' instead of '+'.
     if (lval instanceof SkylarkNestedSet) {
       return new SkylarkNestedSet((SkylarkNestedSet) lval, rval, getLocation());
     }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java b/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java
index 90b40e3..9d14886 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java
@@ -698,6 +698,10 @@
         addToken(new Token(TokenKind.MINUS, pos - 1, pos));
         break;
       }
+      case '|': {
+        addToken(new Token(TokenKind.PIPE, pos - 1, pos));
+        break;
+      }
       case '=': {
         addToken(new Token(TokenKind.EQUALS, pos - 1, pos));
         break;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Operator.java b/src/main/java/com/google/devtools/build/lib/syntax/Operator.java
index 73b37cf..218eee8 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Operator.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Operator.java
@@ -33,6 +33,7 @@
   NOT_IN("not in"),
   OR("or"),
   PERCENT("%"),
+  PIPE("|"),
   PLUS("+");
 
   private final String name;
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 8f59047..6334673 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
@@ -134,6 +134,7 @@
           .put(TokenKind.PERCENT, Operator.PERCENT)
           .put(TokenKind.SLASH, Operator.DIVIDE)
           .put(TokenKind.PLUS, Operator.PLUS)
+          .put(TokenKind.PIPE, Operator.PIPE)
           .put(TokenKind.STAR, Operator.MULT)
           .build();
 
@@ -151,6 +152,7 @@
       EnumSet.of(Operator.NOT),
       EnumSet.of(Operator.EQUALS_EQUALS, Operator.NOT_EQUALS, Operator.LESS, Operator.LESS_EQUALS,
           Operator.GREATER, Operator.GREATER_EQUALS, Operator.IN, Operator.NOT_IN),
+      EnumSet.of(Operator.PIPE),
       EnumSet.of(Operator.MINUS, Operator.PLUS),
       EnumSet.of(Operator.DIVIDE, Operator.MULT, Operator.PERCENT));
 
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/TokenKind.java b/src/main/java/com/google/devtools/build/lib/syntax/TokenKind.java
index 0ada226..18ba037 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/TokenKind.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/TokenKind.java
@@ -67,6 +67,7 @@
   OUTDENT("outdent"),
   PASS("pass"),
   PERCENT("%"),
+  PIPE("|"),
   PLUS("+"),
   PLUS_EQUALS("+="),
   RAISE("raise"),
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTestCase.java b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTestCase.java
index 7be1c86..b2d661b 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTestCase.java
@@ -128,7 +128,7 @@
     setFailFast(true);
     try {
       eval(input);
-      fail();
+      fail("Expected error '" + msg + "' but got no error");
     } catch (IllegalArgumentException | EvalException e) {
       assertThat(e).hasMessage(msg);
     }
@@ -137,7 +137,7 @@
   public void checkEvalErrorContains(String msg, String... input) throws Exception {
     try {
       eval(input);
-      fail();
+      fail("Expected error containing '" + msg + "' but got no error");
     } catch (IllegalArgumentException | EvalException e) {
       assertThat(e.getMessage()).contains(msg);
     }
@@ -146,7 +146,7 @@
   public void checkEvalErrorStartsWith(String msg, String... input) throws Exception {
     try {
       eval(input);
-      fail();
+      fail("Expected error starting with '" + msg + "' but got no error");
     } catch (IllegalArgumentException | EvalException e) {
       assertThat(e.getMessage()).startsWith(msg);
     }
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 e794aa7..22c7153 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
@@ -609,6 +609,14 @@
   }
 
   @Test
+  public void testUnionSet() throws Exception {
+    new SkylarkTest()
+        .testStatement("str(set([1, 3]) | set([1, 2]))", "set([1, 2, 3])")
+        .testStatement("str(set([1, 2]) | [1, 3])", "set([1, 2, 3])")
+        .testIfExactError("unsupported operand type(s) for |: 'int' and 'int'", "2 | 4");
+  }
+
+  @Test
   public void testClassObjectCannotAccessNestedSet() throws Exception {
     new SkylarkTest()
         .update("mock", new MockClassObject())