bazel syntax: add 'truth' method to base SkylarkValue interface

Also:
- rename EvalUtils.toBoolean to Starlark.truth
- make it strict about argument validity
- replace cases with method call
PiperOrigin-RevId: 278917917
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
index 3ce1002..1f1fab3 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
@@ -57,6 +57,7 @@
 import com.google.devtools.build.lib.syntax.SkylarkDict;
 import com.google.devtools.build.lib.syntax.SkylarkList;
 import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
+import com.google.devtools.build.lib.syntax.Starlark;
 import com.google.devtools.build.lib.syntax.StarlarkSemantics;
 import com.google.devtools.build.lib.syntax.StarlarkThread;
 import com.google.devtools.build.lib.vfs.PathFragment;
@@ -613,7 +614,7 @@
     if (progressMessage != Runtime.NONE) {
       builder.setProgressMessageNonLazy((String) progressMessage);
     }
-    if (EvalUtils.toBoolean(useDefaultShellEnv)) {
+    if (Starlark.truth(useDefaultShellEnv)) {
       builder.useDefaultShellEnvironment();
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java
index 5196387..e758d8a 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java
@@ -87,6 +87,7 @@
 import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
 import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
 import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
+import com.google.devtools.build.lib.syntax.Starlark;
 import com.google.devtools.build.lib.syntax.StarlarkSemantics;
 import com.google.devtools.build.lib.syntax.StarlarkThread;
 import com.google.devtools.build.lib.vfs.PathFragment;
@@ -1000,10 +1001,10 @@
         new Runfiles.Builder(
             getRuleContext().getWorkspaceName(), getConfiguration().legacyExternalRunfiles());
     boolean checkConflicts = false;
-    if (EvalUtils.toBoolean(collectData)) {
+    if (Starlark.truth(collectData)) {
       builder.addRunfiles(getRuleContext(), RunfilesProvider.DATA_RUNFILES);
     }
-    if (EvalUtils.toBoolean(collectDefault)) {
+    if (Starlark.truth(collectDefault)) {
       builder.addRunfiles(getRuleContext(), RunfilesProvider.DEFAULT_RUNFILES);
     }
     if (!files.isEmpty()) {
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/ThreadHandler.java b/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/ThreadHandler.java
index b25522b..9174a28 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/ThreadHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkdebug/server/ThreadHandler.java
@@ -28,6 +28,7 @@
 import com.google.devtools.build.lib.syntax.EvalUtils;
 import com.google.devtools.build.lib.syntax.ParserInput;
 import com.google.devtools.build.lib.syntax.Runtime;
+import com.google.devtools.build.lib.syntax.Starlark;
 import com.google.devtools.build.lib.syntax.StarlarkThread;
 import com.google.devtools.build.lib.syntax.SyntaxError;
 import com.google.devtools.build.lib.vfs.PathFragment;
@@ -383,7 +384,7 @@
       return true;
     }
     try {
-      return EvalUtils.toBoolean(doEvaluate(thread, condition));
+      return Starlark.truth(doEvaluate(thread, condition));
     } catch (SyntaxError | EvalException | InterruptedException e) {
       throw new ConditionalBreakpointException(e.getMessage());
     }
diff --git a/src/main/java/com/google/devtools/build/lib/skylarkinterface/SkylarkValue.java b/src/main/java/com/google/devtools/build/lib/skylarkinterface/SkylarkValue.java
index f44420c..5a48206 100644
--- a/src/main/java/com/google/devtools/build/lib/skylarkinterface/SkylarkValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skylarkinterface/SkylarkValue.java
@@ -59,6 +59,11 @@
     str(printer);
   }
 
+  /** Returns the truth-value of this Starlark value. */
+  default boolean truth() {
+    return true;
+  }
+
   /**
    * Returns if the value is immutable.
    *
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Eval.java b/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
index 9404440..2d6a784 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
@@ -152,7 +152,7 @@
   }
 
   private TokenKind execIf(IfStatement node) throws EvalException, InterruptedException {
-    boolean cond = EvalUtils.toBoolean(eval(thread, node.getCondition()));
+    boolean cond = Starlark.truth(eval(thread, node.getCondition()));
     if (cond) {
       return execStatementsInternal(node.getThenBlock());
     } else if (node.getElseBlock() != null) {
@@ -422,9 +422,9 @@
           // AND and OR require short-circuit evaluation.
           switch (binop.getOperator()) {
             case AND:
-              return EvalUtils.toBoolean(x) ? eval(thread, binop.getY()) : x;
+              return Starlark.truth(x) ? eval(thread, binop.getY()) : x;
             case OR:
-              return EvalUtils.toBoolean(x) ? x : eval(thread, binop.getY());
+              return Starlark.truth(x) ? x : eval(thread, binop.getY());
             default:
               Object y = eval(thread, binop.getY());
               return EvalUtils.binaryOp(binop.getOperator(), x, y, thread, binop.getLocation());
@@ -438,7 +438,7 @@
         {
           ConditionalExpression cond = (ConditionalExpression) expr;
           Object v = eval(thread, cond.getCondition());
-          return eval(thread, EvalUtils.toBoolean(v) ? cond.getThenCase() : cond.getElseCase());
+          return eval(thread, Starlark.truth(v) ? cond.getThenCase() : cond.getElseCase());
         }
 
       case DICT_EXPR:
@@ -659,7 +659,7 @@
 
           } else {
             Comprehension.If ifClause = (Comprehension.If) clause;
-            if (EvalUtils.toBoolean(eval(thread, ifClause.getCondition()))) {
+            if (Starlark.truth(eval(thread, ifClause.getCondition()))) {
               execClauses(index + 1);
             }
           }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
index 676d0be..a533cec 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
@@ -17,7 +17,6 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-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;
@@ -305,34 +304,6 @@
     return obj;
   }
 
-  /**
-   * Returns the truth value of an object, according to Python rules.
-   * http://docs.python.org/2/library/stdtypes.html#truth-value-testing
-   */
-  // TODO(adonovan): rename 'Skylark.truth', make it a default-true method of SkylarkValue,
-  // and delete most of the cases.
-  public static boolean toBoolean(Object o) {
-    if (o == null || o == Runtime.NONE) {
-      return false;
-    } else if (o instanceof Boolean) {
-      return (Boolean) o;
-    } else if (o instanceof String) {
-      return !((String) o).isEmpty();
-    } else if (o instanceof Integer) {
-      return (Integer) o != 0;
-    } else if (o instanceof Collection<?>) {
-      return !((Collection<?>) o).isEmpty();
-    } else if (o instanceof Map<?, ?>) {
-      return !((Map<?, ?>) o).isEmpty();
-    } else if (o instanceof SkylarkNestedSet) {
-      return !((SkylarkNestedSet) o).isEmpty();
-    } else if (o instanceof Iterable<?>) {
-      return !Iterables.isEmpty((Iterable<?>) o);
-    } else {
-      return true;
-    }
-  }
-
   public static Collection<?> toCollection(Object o, Location loc, @Nullable StarlarkThread thread)
       throws EvalException {
     if (o instanceof Collection) {
@@ -1056,7 +1027,7 @@
       throws EvalException, InterruptedException {
     switch (op) {
       case NOT:
-        return !toBoolean(x);
+        return !Starlark.truth(x);
 
       case MINUS:
         if (x instanceof Integer) {
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 6d6758c..89eb9c2 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
@@ -155,7 +155,7 @@
       Object collection, boolean value, Location loc, StarlarkThread thread) throws EvalException {
     Iterable<?> iterable = EvalUtils.toIterable(collection, loc, thread);
     for (Object obj : iterable) {
-      if (EvalUtils.toBoolean(obj) == value) {
+      if (Starlark.truth(obj) == value) {
         return true;
       }
     }
@@ -427,7 +427,7 @@
             noneable = true)
       })
   public Boolean bool(Object x) throws EvalException {
-    return EvalUtils.toBoolean(x);
+    return Starlark.truth(x);
   }
 
   private final ImmutableMap<String, Integer> intPrefixes =
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Runtime.java b/src/main/java/com/google/devtools/build/lib/syntax/Runtime.java
index 740fcd8..6338786 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Runtime.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Runtime.java
@@ -65,6 +65,11 @@
     }
 
     @Override
+    public boolean truth() {
+      return false;
+    }
+
+    @Override
     public void repr(SkylarkPrinter printer) {
       printer.append("None");
     }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java
index 0032149..578d926 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java
@@ -50,6 +50,11 @@
   /** Returns true if this list is a Skylark tuple. */
   public abstract boolean isTuple();
 
+  @Override
+  public final boolean truth() {
+    return !isEmpty();
+  }
+
   /**
    * Returns an ImmutableList object with the current underlying contents of this SkylarkList.
    */
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkNestedSet.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkNestedSet.java
index b5be8f9..0c64848 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkNestedSet.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkNestedSet.java
@@ -363,6 +363,11 @@
     return set.isEmpty();
   }
 
+  @Override
+  public boolean truth() {
+    return !set.isEmpty();
+  }
+
   public SkylarkType getContentType() {
     return contentType;
   }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Starlark.java b/src/main/java/com/google/devtools/build/lib/syntax/Starlark.java
index 5e37f8d..aa3b1fe 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Starlark.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Starlark.java
@@ -13,6 +13,7 @@
 // limitations under the License.
 package com.google.devtools.build.lib.syntax;
 
+import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
 import com.google.devtools.build.lib.util.Pair;
 import java.util.Map;
 import java.util.Set;
@@ -23,12 +24,30 @@
  * all clients of the Starlark interpreter.
  */
 // TODO(adonovan): move these here:
-// UNIVERSE, None, len, truth, str, iterate, equal, compare, getattr, index,
+// UNIVERSE, None, len, str, iterate, equal, compare, getattr, index,
 // slice, parse, exec, eval, and so on.
 public final class Starlark {
 
   private Starlark() {} // uninstantiable
 
+  /**
+   * Returns the truth value of a valid Starlark value, as if by the Starlark expression {@code
+   * bool(x)}.
+   */
+  public static boolean truth(Object x) {
+    if (x instanceof Boolean) {
+      return (Boolean) x;
+    } else if (x instanceof SkylarkValue) {
+      return ((SkylarkValue) x).truth();
+    } else if (x instanceof String) {
+      return !((String) x).isEmpty();
+    } else if (x instanceof Integer) {
+      return (Integer) x != 0;
+    } else {
+      throw new IllegalArgumentException("invalid Starlark value: " + x.getClass());
+    }
+  }
+
   // TODO(adonovan):
   //
   // The code below shows the API that is the destination toward which all of the recent
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkMutable.java b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkMutable.java
index 73e8397..789eba3 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkMutable.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkMutable.java
@@ -315,6 +315,11 @@
     }
 
     @Override
+    public final boolean truth() {
+      return !isEmpty();
+    }
+
+    @Override
     public Set<K> keySet() {
       return Collections.unmodifiableMap(getContentsUnsafe()).keySet();
     }