Misc Skylark cleanups and small features
--
MOS_MIGRATED_REVID=88224368
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Type.java b/src/main/java/com/google/devtools/build/lib/packages/Type.java
index fb6a04f..633f0e6 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Type.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Type.java
@@ -84,14 +84,32 @@
// this over selectableConvert.
/**
- * Equivalent to <code>convert(x, null)</code>. Useful for converting values to types that do not
- * involve the type <code>LABEL</code> and hence do not require the label of the current package.
+ * Equivalent to {@code convert(x, what, null)}. Useful for converting values to types that do not
+ * involve the type {@code LABEL} and hence do not require the label of the current package.
*/
public final T convert(Object x, String what) throws ConversionException {
return convert(x, what, null);
}
/**
+ * Like {@code convert(x, what, label)}, but converts skylark {@code None} to java {@code null}.
+ */
+ @Nullable public final T convertOptional (Object x, String what, @Nullable Label currentRule)
+ throws ConversionException {
+ if (EvalUtils.isNullOrNone(x)) {
+ return null;
+ }
+ return convert(x, what, currentRule);
+ }
+
+ /**
+ * Like {@code convert(x, what)}, but converts skylark {@code NONE} to java {@code null}.
+ */
+ @Nullable public final T convertOptional(Object x, String what) throws ConversionException {
+ return convertOptional(x, what, null);
+ }
+
+ /**
* Variation of {@link #convert} that supports selector expressions for configurable attributes
* (i.e. "{ config1: 'value1_of_orig_type', config2: 'value2_of_orig_type; }"). If x is a
* selector expression, returns a {@link Selector} instance that contains key-mapped entries
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkModules.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkModules.java
index ae81f81..a5dc760 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkModules.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkModules.java
@@ -37,6 +37,8 @@
/**
* A class to handle all Skylark modules, to create and setup Validation and regular Environments.
*/
+// TODO(bazel-team): move that to syntax/ and
+// let each extension register itself in a static { } statement.
public class SkylarkModules {
public static final ImmutableList<Class<?>> MODULES = ImmutableList.of(
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 59aadd1..c03a9ff 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
@@ -138,11 +138,14 @@
// TODO(bazel-team): move the following few type-related functions to SkylarkType
/**
- * Compute the super-class of a class that Skylark considers as the Skylark type of its instances
+ * Return the Skylark-type of {@code c}
*
- * <p>Skylark type validation isn't otherwise equipped to deal with inheritance, so we must tell
- * it which is the super-class or interface that matters for Skylark type compatibility.
- * e.g. instances of all subclasses of SkylarkList are considered as being of type SkylarkList.
+ * <p>The result will be a type that Skylark understands and is either equal to {@code c}
+ * or is a supertype of it. For example, all instances of (all subclasses of) SkylarkList
+ * are considered to be SkylarkLists.
+ *
+ * <p>Skylark's type validation isn't equipped to deal with inheritance so we must tell it which
+ * of the superclasses or interfaces of {@code c} is the one that matters for type compatibility.
*
* @param c a class
* @return a super-class of c to be used in validation-time type inference.
@@ -162,7 +165,8 @@
} else if (Set.class.isAssignableFrom(c)) {
return Set.class;
} else {
- // TODO(bazel-team): also unify all ClassObject, that we print the same?
+ // TODO(bazel-team): also unify all implementations of ClassObject,
+ // that we used to all print the same as "struct"?
//
// Check if one of the superclasses or implemented interfaces has the SkylarkModule
// annotation. If yes return that class.
@@ -271,7 +275,7 @@
private static void printValueX(Object o, Appendable buffer)
throws IOException {
if (o == null) {
- throw new NullPointerException(); // None is not a build language value.
+ throw new NullPointerException(); // Java null is not a build language value.
} else if (o instanceof String || o instanceof Integer || o instanceof Double) {
buffer.append(o.toString());
@@ -619,7 +623,7 @@
}
/**
- * Returns the size of the Skylark object or -1 in case the object doesn't have a size.
+ * @return the size of the Skylark object or -1 in case the object doesn't have a size.
*/
public static int size(Object arg) {
if (arg instanceof String) {
@@ -634,4 +638,40 @@
}
return -1;
}
+
+ /** @return true if x is Java null or Skylark None */
+ public static boolean isNullOrNone(Object x) {
+ return x == null || x == Environment.NONE;
+ }
+
+ /**
+ * Build a map of kwarg arguments from a list, removing null-s or None-s.
+ *
+ * @param init a series of key, value pairs (as consecutive arguments), and optionally
+ * a lone map at the end, as in {@code optionMap(k1, v1, k2, v2, k3, v3, map)}
+ * where each key is a String, each value is an arbitrary Objet, and the map
+ * must be a {@code Map<String, Object>}.
+ * @return a {@code Map<String, Object>} that has all the specified entries,
+ * where key, value pairs appearing earlier have precedence,
+ * i.e. {@code k1, v1} may override {@code k3, v3}.
+ *
+ * Ignore any entry the key or value of which is null or None.
+ */
+ @SuppressWarnings("unchecked")
+ public static ImmutableMap<String, Object> optionMap(Object... init) {
+ ImmutableMap.Builder<String, Object> b = new ImmutableMap.Builder<>();
+ int l = init.length;
+ if (l % 2 == 1) { // If there's an odd number of argument, the last one is a Map.
+ l--;
+ b.putAll((Map<String, Object>) init[l]);
+ }
+ for (int i = l - 2; i >= 0; i -= 2) {
+ String key = (String) init[i];
+ Object value = init[i + 1];
+ if (!(isNullOrNone(key) || isNullOrNone(value))) {
+ b.put(key, value);
+ }
+ }
+ return b.build();
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
index 76bb559..c31d434 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java
@@ -470,8 +470,8 @@
// we'd still have to have a HashMap on the side for the sake of properly handling duplicates.
Map<String, Object> kwargs = new HashMap<>();
- final Object returnValue;
- final Function function;
+ Object returnValue;
+ Function function;
if (obj != null) { // obj.func(...)
Object objValue = obj.eval(env);
// Strings, lists and dictionaries (maps) have functions that we want to use in MethodLibrary.
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FunctionSignature.java b/src/main/java/com/google/devtools/build/lib/syntax/FunctionSignature.java
index 8b0f5b0..0208cd7 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/FunctionSignature.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/FunctionSignature.java
@@ -344,7 +344,7 @@
}
public void type(int i) {
if (types != null && types.get(i) != null) {
- sb.append(" : ").append(types.get(i).toString());
+ sb.append(": ").append(types.get(i).toString());
}
}
public void mandatory(int i) {
@@ -355,7 +355,7 @@
public void optional(int i) {
mandatory(i);
sb.append(" = ").append((defaultValues == null)
- ? "null" : String.valueOf(defaultValues.get(j++)));
+ ? "?" : EvalUtils.prettyPrintValue(defaultValues.get(j++)));
}
};
Show show = new Show();
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 42cf163..305ccbe 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
@@ -407,10 +407,21 @@
}
/**
- * Returns a Skylark tuple containing elements.
+ * Build a Skylark tuple from a Java List
+ * @param elements a List of objects
+ * @return a Skylark tuple containing the specified List of objects as elements.
*/
public static SkylarkList tuple(List<?> elements) {
// Tuple elements do not have to have the same type.
return new SimpleSkylarkList(ImmutableList.copyOf(elements), true, SkylarkType.TOP);
}
+
+ /**
+ * Build a Skylark tuple from a variable number of arguments
+ * @param elements a variable number of arguments (or an array of objects)
+ * @return a Skylark tuple containing the specified arguments as elements.
+ */
+ public static SkylarkList tuple(Object... elements) {
+ return new SimpleSkylarkList(ImmutableList.copyOf(elements), true, SkylarkType.TOP);
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java
index d98062b..1bd5908 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java
@@ -287,11 +287,6 @@
simple = TOP;
} else if (type == Empty.class) {
simple = BOTTOM;
- } else if (type == Environment.NoneType.class) {
- // For the purpose of validation-time type inference, treat NONE as being of type TOP,
- // i.e. None like null in Java is in every type.
- // TODO(bazel-team): Should we have .contains also always return true for NONE?
- simple = TOP;
} else {
// Consider all classes that have the same EvalUtils.getSkylarkType() as equivalent,
// as a substitute to handling inheritance.
@@ -549,7 +544,7 @@
}
public boolean contains(Object value) {
- // This returns true a bit too much, but it looks
+ // This returns true a bit too much, not looking at the result type.
return value instanceof Function;
}
@@ -591,6 +586,17 @@
// Utility functions regarding types
+ /**
+ * For the purpose of type inference during validation,
+ * we upgrade the type for None as being Top, the type of everything,
+ * so None is compatible with anything as far as the validate method is concern.
+ *
+ * @param type a SkylarkType suitable for runtime type checking.
+ * @return the corresponding SkylarkType suitable for a type validation.
+ */
+ private static SkylarkType typeForInference(SkylarkType type) {
+ return type == NONE ? TOP : type;
+ }
/**
* Returns the stronger type of this and o if they are compatible. Stronger means that
@@ -599,12 +605,12 @@
*
* <p>If they are not compatible an EvalException is thrown.
*/
- SkylarkType infer(SkylarkType o, String name, Location thisLoc, Location originalLoc)
+ SkylarkType infer(SkylarkType other, String name, Location thisLoc, Location originalLoc)
throws EvalException {
- SkylarkType both = intersection(this, o);
+ SkylarkType both = intersection(typeForInference(this), typeForInference(other));
if (both == BOTTOM) {
throw new EvalException(thisLoc, String.format("bad %s: %s is incompatible with %s at %s",
- name, o, this, originalLoc));
+ name, other, this, originalLoc));
} else {
return both;
}
@@ -678,6 +684,24 @@
}
}
+
+ /**
+ * General purpose type-casting facility.
+ *
+ * @param value - the actual value of the parameter
+ * @param type - the expected Class for the value
+ * @param loc - the location info used in the EvalException
+ * @param format - a format String
+ * @param args - arguments to format, in case there's an exception
+ */
+ public static <T> T castOrNull(Object value, Class<T> type,
+ Location loc, String format, Object... args) throws EvalException {
+ if (value == Environment.NONE) {
+ return null;
+ }
+ return SkylarkType.<T>cast(value, type, loc, format, args);
+ }
+
/**
* General purpose type-casting facility.
*
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java b/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java
index 6afb96a..cc69867 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ValidationEnvironment.java
@@ -68,6 +68,17 @@
clonable = false;
}
+ // ValidationEnvironment for a new Environment()
+ private static ImmutableMap<SkylarkType, ImmutableMap<String, SkylarkType>> globalTypes =
+ ImmutableMap.<SkylarkType, ImmutableMap<String, SkylarkType>>of(SkylarkType.GLOBAL,
+ new ImmutableMap.Builder<String, SkylarkType> ()
+ .put("False", SkylarkType.BOOL).put("True", SkylarkType.BOOL)
+ .put("None", SkylarkType.TOP).build());
+
+ public ValidationEnvironment() {
+ this(globalTypes);
+ }
+
@Override
public ValidationEnvironment clone() {
Preconditions.checkState(clonable);