aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java9
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java27
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java15
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/DictComprehension.java10
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java4
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/ListComprehension.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/ListLiteral.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SkylarkFunction.java56
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java131
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SkylarkNestedSet.java73
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java687
11 files changed, 750 insertions, 266 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
index ae79a17103..cf57d700d0 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
@@ -364,7 +364,8 @@ public final class RuleConfiguredTargetBuilder {
private void checkCompositeSkylarkObjectSafe(Object object) {
if (object instanceof SkylarkList) {
SkylarkList list = (SkylarkList) object;
- if (list == SkylarkList.EMPTY_LIST || isSimpleSkylarkObjectSafe(list.getGenericType())) {
+ if (list == SkylarkList.EMPTY_LIST
+ || isSimpleSkylarkObjectSafe(list.getContentType().getType())) {
// Try not to iterate over the list if avoidable.
return;
}
@@ -375,9 +376,9 @@ public final class RuleConfiguredTargetBuilder {
return;
} else if (object instanceof SkylarkNestedSet) {
// SkylarkNestedSets cannot have composite items.
- Class<?> genericType = ((SkylarkNestedSet) object).getGenericType();
- if (!genericType.equals(Object.class) && !isSimpleSkylarkObjectSafe(genericType)) {
- throw new IllegalArgumentException(EvalUtils.getDataTypeName(genericType));
+ Class<?> contentType = ((SkylarkNestedSet) object).getContentType().getType();
+ if (!contentType.equals(Object.class) && !isSimpleSkylarkObjectSafe(contentType)) {
+ throw new IllegalArgumentException(EvalUtils.getDataTypeName(contentType));
}
return;
} else if (object instanceof Map<?, ?>) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
index 489621122f..f848f91c2e 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleConfiguredTargetBuilder.java
@@ -33,10 +33,11 @@ import com.google.devtools.build.lib.syntax.ClassObject;
import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
import com.google.devtools.build.lib.syntax.Environment;
import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.syntax.EvalUtils;
import com.google.devtools.build.lib.syntax.Function;
import com.google.devtools.build.lib.syntax.SkylarkEnvironment;
-import com.google.devtools.build.lib.syntax.SkylarkFunction;
import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
+import com.google.devtools.build.lib.syntax.SkylarkType;
/**
* A helper class to build Rule Configured Targets via runtime loaded rule implementations
@@ -138,7 +139,7 @@ public final class SkylarkRuleConfiguredTargetBuilder {
private static ConfiguredTarget addStructFields(RuleContext ruleContext,
RuleConfiguredTargetBuilder builder, Object target, Artifact executable)
- throws EvalException {
+ throws EvalException {
Location loc = null;
Runfiles statelessRunfiles = null;
Runfiles dataRunfiles = null;
@@ -152,7 +153,7 @@ public final class SkylarkRuleConfiguredTargetBuilder {
builder.setFilesToBuild(cast("files", struct, SkylarkNestedSet.class, Artifact.class, loc)
.getSet(Artifact.class));
} else if (key.equals("runfiles")) {
- statelessRunfiles = cast("runfiles", struct, Runfiles.class, loc);
+ statelessRunfiles = cast("runfiles", struct, Runfiles.class, loc);
} else if (key.equals("data_runfiles")) {
dataRunfiles = cast("data_runfiles", struct, Runfiles.class, loc);
} else if (key.equals("default_runfiles")) {
@@ -201,16 +202,22 @@ public final class SkylarkRuleConfiguredTargetBuilder {
}
}
- private static <T> T cast(String paramName, ClassObject struct, Class<T> expectedType,
- Location loc) throws EvalException {
- return cast(paramName, struct, expectedType, Object.class, loc);
+ private static <T> T cast(String paramName, ClassObject struct, Class<T> expectedGenericType,
+ Class<?> expectedArgumentType, Location loc) throws EvalException {
+ Object value = struct.getValue(paramName);
+ return SkylarkType.cast(value, expectedGenericType, expectedArgumentType, loc,
+ "expected %s for '%s' but got %s instead: %s",
+ SkylarkType.of(expectedGenericType, expectedArgumentType),
+ paramName, EvalUtils.getDataTypeName(value, true), value);
}
private static <T> T cast(String paramName, ClassObject struct, Class<T> expectedType,
- Class<?> expectedGenericType, Location loc) throws EvalException {
- return SkylarkFunction.cast("rule_implementation.return", paramName,
- expectedType, expectedGenericType, struct.getValue(paramName), loc,
- "'" + paramName + "' field of the struct returned by the rule implementation function");
+ Location loc) throws EvalException {
+ Object value = struct.getValue(paramName);
+ return SkylarkType.cast(value, expectedType, loc,
+ "expected %s for '%s' but got %s instead: %s",
+ SkylarkType.of(expectedType),
+ paramName, EvalUtils.getDataTypeName(value, false), value);
}
private static Runfiles merge(Runfiles runfiles, Artifact executable) {
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 d7c142713e..778bc7bd8b 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
@@ -285,8 +285,6 @@ public final class BinaryOperatorExpression extends Expression {
SkylarkType validate(ValidationEnvironment env) throws EvalException {
SkylarkType ltype = lhs.validate(env);
SkylarkType rtype = rhs.validate(env);
- String lname = EvalUtils.getDataTypeNameFromClass(ltype.getType());
- String rname = EvalUtils.getDataTypeNameFromClass(rtype.getType());
switch (operator) {
case AND: {
@@ -320,19 +318,19 @@ public final class BinaryOperatorExpression extends Expression {
// struct + struct
if (ltype.isStruct() && rtype.isStruct()) {
- return SkylarkType.of(ClassObject.class);
+ return SkylarkType.STRUCT;
}
if (ltype.isNset()) {
if (rtype.isNset()) {
return ltype.infer(rtype, "nested set", rhs.getLocation(), lhs.getLocation());
} else if (rtype.isList()) {
- return ltype.infer(SkylarkType.of(SkylarkNestedSet.class, rtype.getGenericType1()),
+ return ltype.infer(SkylarkType.of(SkylarkType.SET, rtype.getArgType()),
"nested set", rhs.getLocation(), lhs.getLocation());
}
if (rtype != SkylarkType.UNKNOWN) {
throw new EvalException(getLocation(), String.format("can only concatenate nested sets "
- + "with other nested sets or list of items, not '" + rname + "'"));
+ + "with other nested sets or list of items, not '%s'", rtype));
}
}
@@ -384,7 +382,7 @@ public final class BinaryOperatorExpression extends Expression {
case GREATER:
case GREATER_EQUALS: {
if (ltype != SkylarkType.UNKNOWN && !(Comparable.class.isAssignableFrom(ltype.getType()))) {
- throw new EvalException(getLocation(), lname + " is not comparable");
+ throw new EvalException(getLocation(), ltype + " is not comparable");
}
ltype.infer(rtype, "comparison", lhs.getLocation(), rhs.getLocation());
return SkylarkType.BOOL;
@@ -399,8 +397,7 @@ public final class BinaryOperatorExpression extends Expression {
} else {
if (rtype != SkylarkType.UNKNOWN) {
throw new EvalException(getLocation(), String.format("operand 'in' only works on "
- + "strings, dictionaries, lists, sets or tuples, not on a(n) %s",
- EvalUtils.getDataTypeNameFromClass(rtype.getType())));
+ + "strings, dictionaries, lists, sets or tuples, not on a(n) %s", rtype));
}
}
}
@@ -411,7 +408,7 @@ public final class BinaryOperatorExpression extends Expression {
if (ltype != SkylarkType.UNKNOWN && rtype != SkylarkType.UNKNOWN) {
throw new EvalException(getLocation(),
String.format("unsupported operand type(s) for %s: '%s' and '%s'",
- operator, lname, rname));
+ operator, ltype, rtype));
}
return SkylarkType.UNKNOWN;
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/DictComprehension.java b/src/main/java/com/google/devtools/build/lib/syntax/DictComprehension.java
index a69605e4a0..05190ab1e8 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/DictComprehension.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/DictComprehension.java
@@ -16,7 +16,6 @@ package com.google.devtools.build.lib.syntax;
import com.google.common.collect.ImmutableMap;
import java.util.LinkedHashMap;
-import java.util.Map;
/**
* Syntax node for dictionary comprehension expressions.
@@ -68,11 +67,8 @@ public class DictComprehension extends Expression {
@Override
SkylarkType validate(ValidationEnvironment env) throws EvalException {
SkylarkType elementsType = listExpression.validate(env);
- // TODO(bazel-team): GenericType1 should be a SkylarkType.
- Class<?> listElementType = elementsType.getGenericType1();
- SkylarkType listElementSkylarkType = listElementType.equals(Object.class)
- ? SkylarkType.UNKNOWN : SkylarkType.of(listElementType);
- env.update(loopVar.getName(), listElementSkylarkType, getLocation());
+ SkylarkType listElementType = SkylarkType.getGenericArgType(elementsType);
+ env.update(loopVar.getName(), listElementType, getLocation());
SkylarkType keyType = keyExpression.validate(env);
if (!keyType.isSimple()) {
// TODO(bazel-team): this is most probably dead code but it's better to have it here
@@ -83,7 +79,7 @@ public class DictComprehension extends Expression {
if (elementsType != SkylarkType.UNKNOWN && !elementsType.isList()) {
throw new EvalException(getLocation(), "Dict comprehension elements must be a list");
}
- return SkylarkType.of(Map.class, keyType.getType());
+ return SkylarkType.of(SkylarkType.MAP, keyType);
}
@Override
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 02c10a26e1..9448c32b2b 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
@@ -187,11 +187,11 @@ public abstract class EvalUtils {
if (list.isTuple()) {
return "tuple";
} else {
- return "list" + (full ? " of " + list.getGenericType() + "s" : "");
+ return "list" + (full ? " of " + list.getContentType() + "s" : "");
}
} else if (object instanceof SkylarkNestedSet) {
SkylarkNestedSet set = (SkylarkNestedSet) object;
- return "set" + (full ? " of " + set.getGenericType() + "s" : "");
+ return "set" + (full ? " of " + set.getContentType() + "s" : "");
} else {
return getDataTypeNameFromClass(object.getClass());
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ListComprehension.java b/src/main/java/com/google/devtools/build/lib/syntax/ListComprehension.java
index 6a13ba846b..c48fe64319 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ListComprehension.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ListComprehension.java
@@ -128,6 +128,6 @@ public final class ListComprehension extends Expression {
env.update(list.getKey().getName(), SkylarkType.UNKNOWN, getLocation());
}
elementExpression.validate(env);
- return SkylarkType.of(SkylarkList.class);
+ return SkylarkType.LIST;
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ListLiteral.java b/src/main/java/com/google/devtools/build/lib/syntax/ListLiteral.java
index d8c7811d30..fc43c2fbcd 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ListLiteral.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ListLiteral.java
@@ -129,6 +129,6 @@ public final class ListLiteral extends Expression {
type = type.infer(nextType, "list literal", expr.getLocation(), getLocation());
}
}
- return SkylarkType.of(SkylarkList.class, type.getType());
+ return SkylarkType.of(SkylarkType.LIST, type);
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkFunction.java
index e6959e8fe2..ecfd660c48 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkFunction.java
@@ -162,55 +162,19 @@ public abstract class SkylarkFunction extends AbstractFunction {
arguments.put(paramName, value);
return;
}
- cast(getName(), paramName, param.type(), param.generic1(), value, loc, param.doc());
+ checkType(getName(), paramName, SkylarkType.of(param.type(), param.generic1()),
+ value, loc, param.doc());
arguments.put(paramName, value);
}
- /**
- * Throws an EvalException of realValue is not of the expected type, otherwise returns realValue.
- *
- * @param functionName - name of the function
- * @param paramName - name of the parameter
- * @param expectedType - the expected type of the parameter
- * @param expectedGenericType - the expected generic type of the parameter, or
- * Object.class if undefined
- * @param realValue - the actual value of the parameter
- * @param loc - the location info used in the EvalException
- * @param paramDoc - the documentation of the parameter to print in the error message
- */
- @SuppressWarnings("unchecked")
- public static <T> T cast(String functionName, String paramName,
- Class<T> expectedType, Class<?> expectedGenericType,
- Object realValue, Location loc, String paramDoc) throws EvalException {
- if (!(expectedType.isAssignableFrom(realValue.getClass()))) {
- throw new EvalException(loc, String.format("expected %s for '%s' but got %s instead\n"
- + "%s.%s: %s",
- EvalUtils.getDataTypeNameFromClass(expectedType), paramName,
- EvalUtils.getDataTypeName(realValue), functionName, paramName, paramDoc));
- }
- if (expectedType.equals(SkylarkList.class)) {
- checkGeneric(functionName, paramName, expectedType, expectedGenericType,
- realValue, ((SkylarkList) realValue).getGenericType(), loc, paramDoc);
- } else if (expectedType.equals(SkylarkNestedSet.class)) {
- checkGeneric(functionName, paramName, expectedType, expectedGenericType,
- realValue, ((SkylarkNestedSet) realValue).getGenericType(), loc, paramDoc);
- }
- return (T) realValue;
- }
-
- private static void checkGeneric(String functionName, String paramName,
- Class<?> expectedType, Class<?> expectedGenericType,
- Object realValue, Class<?> realGenericType,
- Location loc, String paramDoc) throws EvalException {
- if (!realGenericType.equals(Object.class)
- && !expectedGenericType.isAssignableFrom(realGenericType)) {
- String mainType = EvalUtils.getDataTypeNameFromClass(expectedType);
- throw new EvalException(loc, String.format(
- "expected %s of %ss for '%s' but got %s of %ss instead\n%s.%s: %s",
- mainType, EvalUtils.getDataTypeNameFromClass(expectedGenericType),
- paramName,
- EvalUtils.getDataTypeName(realValue), EvalUtils.getDataTypeNameFromClass(realGenericType),
- functionName, paramName, paramDoc));
+ public static void checkType(String functionName, String paramName,
+ SkylarkType type, Object value, Location loc, String paramDoc) throws EvalException {
+ if (type != null && value != null) { // TODO(bazel-team): should we give a pass to NONE here?
+ if (!type.contains(value)) {
+ throw new EvalException(loc, String.format(
+ "expected %s for '%s' while calling %s but got %s instead: %s",
+ type, paramName, functionName, EvalUtils.getDataTypeName(value, true), value));
+ }
}
}
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 5e00f057ee..c30824f1af 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
@@ -40,15 +40,15 @@ import java.util.List;
+ "List elements have to be of the same type, <code>[1, 2, \"c\"]</code> results in an "
+ "error. Lists - just like everything - are immutable, therefore <code>x[1] = \"a\""
+ "</code> is not supported.")
- // TODO(bazel-team): should we instead implements List<Object> like ImmutableList does?
- public abstract class SkylarkList implements Iterable<Object> {
+// TODO(bazel-team): should we instead have it implement List<Object> like ImmutableList does?
+public abstract class SkylarkList implements Iterable<Object> {
private final boolean tuple;
- private final Class<?> genericType;
+ private final SkylarkType contentType;
- private SkylarkList(boolean tuple, Class<?> genericType) {
+ private SkylarkList(boolean tuple, SkylarkType contentType) {
this.tuple = tuple;
- this.genericType = genericType;
+ this.contentType = contentType;
}
/**
@@ -74,8 +74,8 @@ import java.util.List;
}
@VisibleForTesting
- public Class<?> getGenericType() {
- return genericType;
+ public SkylarkType getContentType() {
+ return contentType;
}
@Override
@@ -88,17 +88,17 @@ import java.util.List;
/**
* Converts this Skylark list to a Java list.
*/
- public abstract List<?> toList();
+ public abstract List<Object> toList();
@SuppressWarnings("unchecked")
public <T> Iterable<T> to(Class<T> type) {
- Preconditions.checkArgument(this == EMPTY_LIST || type.isAssignableFrom(genericType));
+ Preconditions.checkArgument(this == EMPTY_LIST || contentType.canBeCastTo(type));
return (Iterable<T>) this;
}
private static final class EmptySkylarkList extends SkylarkList {
private EmptySkylarkList(boolean tuple) {
- super(tuple, Object.class);
+ super(tuple, SkylarkType.TOP);
}
@Override
@@ -122,7 +122,7 @@ import java.util.List;
}
@Override
- public List<?> toList() {
+ public List<Object> toList() {
return isTuple() ? ImmutableList.of() : Lists.newArrayList();
}
@@ -137,11 +137,16 @@ import java.util.List;
*/
public static final SkylarkList EMPTY_LIST = new EmptySkylarkList(false);
+ /**
+ * An empty Skylark tuple.
+ */
+ public static final SkylarkList EMPTY_TUPLE = new EmptySkylarkList(true);
+
private static final class SimpleSkylarkList extends SkylarkList {
private final ImmutableList<Object> list;
- private SimpleSkylarkList(ImmutableList<Object> list, boolean tuple, Class<?> genericType) {
- super(tuple, genericType);
+ private SimpleSkylarkList(ImmutableList<Object> list, boolean tuple, SkylarkType contentType) {
+ super(tuple, contentType);
this.list = Preconditions.checkNotNull(list);
}
@@ -166,7 +171,7 @@ import java.util.List;
}
@Override
- public List<?> toList() {
+ public List<Object> toList() {
return isTuple() ? list : Lists.newArrayList(list);
}
@@ -203,8 +208,8 @@ import java.util.List;
private final Iterable<Object> iterable;
private ImmutableList<Object> list = null;
- private LazySkylarkList(Iterable<Object> iterable, boolean tuple, Class<?> genericType) {
- super(tuple, genericType);
+ private LazySkylarkList(Iterable<Object> iterable, boolean tuple, SkylarkType contentType) {
+ super(tuple, contentType);
this.iterable = Preconditions.checkNotNull(iterable);
}
@@ -229,7 +234,7 @@ import java.util.List;
}
@Override
- public List<?> toList() {
+ public List<Object> toList() {
return getList();
}
@@ -250,8 +255,8 @@ import java.util.List;
private final SkylarkList right;
private ConcatenatedSkylarkList(
- SkylarkList left, SkylarkList right, boolean tuple, Class<?> genericType) {
- super(tuple, genericType);
+ SkylarkList left, SkylarkList right, boolean tuple, SkylarkType contentType) {
+ super(tuple, contentType);
this.left = Preconditions.checkNotNull(left);
this.right = Preconditions.checkNotNull(right);
}
@@ -286,59 +291,91 @@ import java.util.List;
}
@Override
- public List<?> toList() {
+ public ImmutableList<Object> toList() {
return ImmutableList.<Object>builder().addAll(left).addAll(right).build();
}
}
/**
- * Returns a Skylark list containing elements without a type check. Only use if all elements
- * are of the same type.
+ * @param elements the contents of the list
+ * @param contentType a SkylarkType for the contents of the list
+ * @return a Skylark list containing elements without a type check
+ * Only use if you already know for sure all elements are of the specified type.
*/
- public static SkylarkList list(Collection<?> elements, Class<?> genericType) {
+ public static SkylarkList list(Collection<?> elements, SkylarkType contentType) {
if (elements.isEmpty()) {
return EMPTY_LIST;
}
- return new SimpleSkylarkList(ImmutableList.copyOf(elements), false, genericType);
+ return new SimpleSkylarkList(ImmutableList.copyOf(elements), false, contentType);
+ }
+
+ /**
+ * @param elements the contents of the list
+ * @param contentType a Java class for the contents of the list
+ * @return a Skylark list containing elements without a type check.
+ * Only use if you already know for sure all elements are of the specified type.
+ */
+ @SuppressWarnings("unchecked")
+ public static SkylarkList list(Collection<?> elements, Class<?> contentType) {
+ return list(elements, SkylarkType.of(contentType));
}
/**
- * Returns a Skylark list containing elements without a type check and without creating
- * an immutable copy. Therefore the iterable containing elements must be immutable
- * (which is not checked here so callers must be extra careful). This way
- * it's possibly to create a SkylarkList without requesting the original iterator. This
- * can be useful for nested set - list conversions.
+ * @param elements the contents of the list
+ * @param contentType a SkylarkType for the contents of the list
+ * @return a Skylark list without a type check and without creating an immutable copy.
+ * Therefore the iterable containing elements must be immutable
+ * (which is not checked here so callers must be extra careful).
+ * This way it's possibly to create a SkylarkList without requesting the original iterator.
+ * This can be useful for nested set - list conversions.
*/
@SuppressWarnings("unchecked")
- public static SkylarkList lazyList(Iterable<?> elements, Class<?> genericType) {
- return new LazySkylarkList((Iterable<Object>) elements, false, genericType);
+ public static SkylarkList lazyList(Iterable<?> elements, SkylarkType contentType) {
+ return new LazySkylarkList((Iterable<Object>) elements, false, contentType);
}
/**
- * Returns a Skylark list containing elements. Performs type check and throws an exception
- * in case the list contains elements of different type.
+ * @param elements the contents of the list
+ * @param contentType a Java class for the contents of the list
+ * @return a Skylark list without a type check and without creating an immutable copy.
+ * Therefore the iterable containing elements must be immutable
+ * (which is not checked here so callers must be extra careful).
+ * This way it's possibly to create a SkylarkList without requesting the original iterator.
+ * This can be useful for nested set - list conversions.
+ */
+ @SuppressWarnings("unchecked")
+ public static SkylarkList lazyList(Iterable<?> elements, Class<?> contentType) {
+ return lazyList(elements, SkylarkType.of(contentType));
+ }
+
+ /**
+ * @param elements the contents of the list
+ * @return a Skylark list containing elements
+ * @throws an EvalException in case the list is not monomorphic
*/
public static SkylarkList list(Collection<?> elements, Location loc) throws EvalException {
if (elements.isEmpty()) {
return EMPTY_LIST;
}
return new SimpleSkylarkList(
- ImmutableList.copyOf(elements), false, getGenericType(elements, loc));
+ ImmutableList.copyOf(elements), false, getContentType(elements, loc));
}
- private static Class<?> getGenericType(Collection<?> elements, Location loc)
+ private static SkylarkType getContentType(Collection<?> elements, Location loc)
throws EvalException {
- Class<?> genericType = elements.iterator().next().getClass();
+ SkylarkType type = SkylarkType.TOP;
for (Object element : elements) {
- Class<?> type = element.getClass();
- if (!EvalUtils.getSkylarkType(genericType).equals(EvalUtils.getSkylarkType(type))) {
+ SkylarkType elementType = SkylarkType.typeOf(element);
+ SkylarkType inter = SkylarkType.intersection(type, elementType);
+ if (inter == SkylarkType.BOTTOM) {
throw new EvalException(loc, String.format(
- "Incompatible types in list: found a %s but the first element is a %s",
- EvalUtils.getDataTypeNameFromClass(type),
- EvalUtils.getDataTypeNameFromClass(genericType)));
+ "Incompatible types in list: found a %s but the previous elements were %ss",
+ elementType, type));
+ } else {
+ type = inter;
}
}
- return genericType;
+ return type;
}
/**
@@ -356,12 +393,12 @@ import java.util.List;
if (right == EMPTY_LIST) {
return left;
}
- if (!left.genericType.equals(right.genericType)) {
+ SkylarkType type = SkylarkType.intersection(left.contentType, right.contentType);
+ if (type == SkylarkType.BOTTOM) {
throw new EvalException(loc, String.format("cannot concatenate list of %s with list of %s",
- EvalUtils.getDataTypeNameFromClass(left.genericType),
- EvalUtils.getDataTypeNameFromClass(right.genericType)));
+ left.contentType, right.contentType));
}
- return new ConcatenatedSkylarkList(left, right, left.isTuple(), left.genericType);
+ return new ConcatenatedSkylarkList(left, right, left.isTuple(), type);
}
/**
@@ -369,6 +406,6 @@ import java.util.List;
*/
public static SkylarkList tuple(List<?> elements) {
// Tuple elements do not have to have the same type.
- return new SimpleSkylarkList(ImmutableList.copyOf(elements), true, Object.class);
+ return new SimpleSkylarkList(ImmutableList.copyOf(elements), true, SkylarkType.TOP);
}
}
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 44729cf23e..5ba7f4d3cc 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
@@ -26,7 +26,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
-import java.util.Map;
import javax.annotation.Nullable;
@@ -47,18 +46,18 @@ import javax.annotation.Nullable;
@Immutable
public final class SkylarkNestedSet implements Iterable<Object> {
- private final Class<?> genericType;
+ private final SkylarkType contentType;
@Nullable private final List<Object> items;
@Nullable private final List<NestedSet<Object>> transitiveItems;
private final NestedSet<?> set;
public SkylarkNestedSet(Order order, Object item, Location loc) throws EvalException {
- this(order, Object.class, item, loc, new ArrayList<Object>(),
+ this(order, SkylarkType.TOP, item, loc, new ArrayList<Object>(),
new ArrayList<NestedSet<Object>>());
}
public SkylarkNestedSet(SkylarkNestedSet left, Object right, Location loc) throws EvalException {
- this(left.set.getOrder(), left.genericType, right, loc,
+ this(left.set.getOrder(), left.contentType, right, loc,
new ArrayList<Object>(checkItems(left.items, loc)),
new ArrayList<NestedSet<Object>>(checkItems(left.transitiveItems, loc)));
}
@@ -76,27 +75,27 @@ public final class SkylarkNestedSet implements Iterable<Object> {
// This is safe because of the type checking
@SuppressWarnings("unchecked")
- private SkylarkNestedSet(Order order, Class<?> genericType, Object item, Location loc,
+ private SkylarkNestedSet(Order order, SkylarkType contentType, Object item, Location loc,
List<Object> items, List<NestedSet<Object>> transitiveItems) throws EvalException {
// Adding the item
if (item instanceof SkylarkNestedSet) {
SkylarkNestedSet nestedSet = (SkylarkNestedSet) item;
if (!nestedSet.isEmpty()) {
- genericType = checkType(genericType, nestedSet.genericType, loc);
+ contentType = checkType(contentType, nestedSet.contentType, loc);
transitiveItems.add((NestedSet<Object>) nestedSet.set);
}
} else if (item instanceof SkylarkList) {
// TODO(bazel-team): we should check ImmutableList here but it screws up genrule at line 43
for (Object object : (SkylarkList) item) {
- genericType = checkType(genericType, object.getClass(), loc);
+ contentType = checkType(contentType, SkylarkType.of(object.getClass()), loc);
items.add(object);
}
} else {
throw new EvalException(loc,
String.format("cannot add '%s'-s to nested sets", EvalUtils.getDataTypeName(item)));
}
- this.genericType = Preconditions.checkNotNull(genericType, "type cannot be null");
+ this.contentType = Preconditions.checkNotNull(contentType, "type cannot be null");
// Initializing the real nested set
NestedSetBuilder<Object> builder = new NestedSetBuilder<Object>(order);
@@ -116,43 +115,55 @@ public final class SkylarkNestedSet implements Iterable<Object> {
/**
* Returns a type safe SkylarkNestedSet. Use this instead of the constructor if possible.
*/
- public static <T> SkylarkNestedSet of(Class<T> genericType, NestedSet<T> set) {
- return new SkylarkNestedSet(genericType, set);
+ public static <T> SkylarkNestedSet of(SkylarkType contentType, NestedSet<T> set) {
+ return new SkylarkNestedSet(contentType, set);
+ }
+
+ /**
+ * Returns a type safe SkylarkNestedSet. Use this instead of the constructor if possible.
+ */
+ public static <T> SkylarkNestedSet of(Class<T> contentType, NestedSet<T> set) {
+ return of(SkylarkType.of(contentType), set);
}
/**
* A not type safe constructor for SkylarkNestedSet. It's discouraged to use it unless type
* generic safety is guaranteed from the caller side.
*/
- SkylarkNestedSet(Class<?> genericType, NestedSet<?> set) {
+ SkylarkNestedSet(SkylarkType contentType, NestedSet<?> set) {
// This is here for the sake of FuncallExpression.
- this.genericType = Preconditions.checkNotNull(genericType, "type cannot be null");
+ this.contentType = Preconditions.checkNotNull(contentType, "type cannot be null");
this.set = Preconditions.checkNotNull(set, "set cannot be null");
this.items = null;
this.transitiveItems = null;
}
- private static Class<?> checkType(Class<?> builderType, Class<?> itemType, Location loc)
+ /**
+ * A not type safe constructor for SkylarkNestedSet, specifying type as a Java class.
+ * It's discouraged to use it unless type generic safety is guaranteed from the caller side.
+ */
+ SkylarkNestedSet(Class<?> contentType, NestedSet<?> set) {
+ this(SkylarkType.of(contentType), set);
+ }
+
+ private static SkylarkType checkType(SkylarkType builderType, SkylarkType itemType, Location loc)
throws EvalException {
- if (Map.class.isAssignableFrom(itemType) || SkylarkList.class.isAssignableFrom(itemType)
- || ClassObject.class.isAssignableFrom(itemType)) {
+ if (SkylarkType.intersection(
+ SkylarkType.Union.of(SkylarkType.MAP, SkylarkType.LIST, SkylarkType.STRUCT),
+ itemType) != SkylarkType.BOTTOM) {
throw new EvalException(loc, String.format("nested set item is composite (type of %s)",
- EvalUtils.getDataTypeNameFromClass(itemType)));
+ itemType));
}
- if (!EvalUtils.isSkylarkImmutable(itemType)) {
+ if (!EvalUtils.isSkylarkImmutable(itemType.getType())) {
throw new EvalException(loc, String.format("nested set item is not immutable (type of %s)",
- EvalUtils.getDataTypeNameFromClass(itemType)));
- }
- if (builderType.equals(Object.class)) {
- return itemType;
+ itemType));
}
- if (!EvalUtils.getSkylarkType(builderType).equals(EvalUtils.getSkylarkType(itemType))) {
+ SkylarkType newType = SkylarkType.intersection(builderType, itemType);
+ if (newType == SkylarkType.BOTTOM) {
throw new EvalException(loc, String.format(
- "nested set item is type of %s but the nested set accepts only %s-s",
- EvalUtils.getDataTypeNameFromClass(itemType),
- EvalUtils.getDataTypeNameFromClass(builderType)));
+ "cannot add an item of type %s to a nested %s", itemType, builderType));
}
- return builderType;
+ return newType;
}
/**
@@ -165,10 +176,10 @@ public final class SkylarkNestedSet implements Iterable<Object> {
if (set.isEmpty()) {
return (NestedSet<T>) set;
}
- Preconditions.checkArgument(type.isAssignableFrom(genericType),
- String.format("Expected %s as a type but got %s",
+ Preconditions.checkArgument(contentType.canBeCastTo(type),
+ String.format("Expected a set of %ss but got a set of %ss",
EvalUtils.getDataTypeNameFromClass(type),
- EvalUtils.getDataTypeNameFromClass(genericType)));
+ contentType));
return (NestedSet<T>) set;
}
@@ -188,8 +199,8 @@ public final class SkylarkNestedSet implements Iterable<Object> {
}
@VisibleForTesting
- public Class<?> getGenericType() {
- return genericType;
+ public SkylarkType getContentType() {
+ return contentType;
}
@Override
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 04c345fc33..2272be68a2 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
@@ -13,7 +13,11 @@
// limitations under the License.
package com.google.devtools.build.lib.syntax;
+import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Interner;
+import com.google.common.collect.Interners;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.events.Location;
@@ -21,6 +25,9 @@ import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -29,162 +36,519 @@ import javax.annotation.Nullable;
/**
* A class representing types available in Skylark.
+ *
+ * <p>A SkylarkType can be one of:
+ * <ul>
+ * <li>a Simple type that contains exactly the objects in a given class,
+ * (including the special TOP and BOTTOM types that respectively contain
+ * all the objects (Simple type for Object.class) and no object at all
+ * (Simple type for EmptyType.class, isomorphic to Void.class).
+ * <li>a Combination of a generic class (one of LIST, MAP, SET)
+ * and an argument type (that itself need not be Simple).
+ * <li>a Union of a finite set of types
+ * <li>a FunctionType associated with a name and a returnType
+ * _that changes during validation_
+ * </ul>
+ *
+ * <p>In a style reminiscent of Java's null, Skylark's None is in all the types
+ * as far as type inference goes, yet actually no type .contains(it).
+ *
+ * <p>The current implementation fails to distinguish between TOP and ANY,
+ * between BOTTOM and EMPTY (VOID, ZERO, FALSE):
+ * <ul>
+ * <li>In type analysis, we often distinguish a notion of "the type of this object"
+ * from the notion of "what I know about the type of this object".
+ * Some languages have a Universal Base Class that contains all objects, and would be the ANY type.
+ * The Skylark runtime, written in Java, has this ANY type, Java's Object.class.
+ * But the Skylark validation engine doesn't really have a concept of an ANY class;
+ * however, it does have a concept of a yet-undermined class, the TOP class
+ * (called UNKOWN in previous code). In the future, we may have to distinguish between the two,
+ * at which point type constructor classes would have to be generic in
+ * "actual type" vs "partial knowledge of type".
+ * <li>Similarly, and EMPTY type (also known as VOID, ZERO or FALSE, in other contexts)
+ * is a type that has no instance, whereas the BOTTOM type is the type analysis that says
+ * that there is no possible runtime type for the given object, which may imply that
+ * the point in the program at which the object is evaluated cannot be reached, etc.
+ * </ul>
+ * So for now, we have puns between TOP and ANY, BOTTOM and EMPTY, between runtime (eval) and
+ * validation-time (validate). Yet in the future, we may need to make a clear distinction,
+ * especially if we are to have types such List(Any) vs List(Top), which contains the former,
+ * but also plenty of other quite distinct types. And yet in a future future, the TOP type
+ * would not be represented explicitly, instead a new type variable would be inserted everywhere
+ * a type is unknown, to be unified with further type information as it becomes available.
*/
-public class SkylarkType {
+// TODO(bazel-team): move the FunctionType side-effect out of the type object
+// and into the validation environment.
+public abstract class SkylarkType {
- private static final class Global {}
+ // The main primitives to override in subclasses
- public static final SkylarkType UNKNOWN = new SkylarkType(Object.class);
- public static final SkylarkType NONE = new SkylarkType(Environment.NoneType.class);
- public static final SkylarkType GLOBAL = new SkylarkType(Global.class);
+ /** Is the given value an element of this type? By default, no (empty type) */
+ public boolean contains(Object value) {
+ return false;
+ }
- public static final SkylarkType STRING = new SkylarkType(String.class);
- public static final SkylarkType INT = new SkylarkType(Integer.class);
- public static final SkylarkType BOOL = new SkylarkType(Boolean.class);
+ /**
+ * intersectWith() is the internal method from which function intersection(t1, t2) is computed.
+ * OVERRIDE this method in your classes, but DO NOT TO CALL it: only call intersection().
+ * When computing intersection(t1, t2), whichever type defined before the other
+ * knows nothing about the other and about their intersection, and returns BOTTOM;
+ * the other knows about the former, and returns their intersection (which may be BOTTOM).
+ * intersection() will call in one order then the other, and return whichever answer
+ * isn't BOTTOM, if any. By default, types are disjoint and their intersection is BOTTOM.
+ */
+ // TODO(bazel-team): should we define and use an Exception instead?
+ protected SkylarkType intersectWith(SkylarkType other) {
+ return BOTTOM;
+ }
- private final Class<?> type;
+ /** @return true if any object of this SkylarkType can be cast to that Java class */
+ public boolean canBeCastTo(Class<?> type) {
+ return Simple.of(type).includes(this);
+ }
- // TODO(bazel-team): Change this to SkylarkType and check generics of generics etc.
- // Object.class is used for UNKNOWN.
- private Class<?> generic1;
+ /** @return true if some non-null objects of that Java class can be cast to this SkylarkType
+ * Note that this is much weaker than an .includes() predicate.
+ */
+ public boolean canBeCastFrom(Class<?> type) {
+ return this.getType().isAssignableFrom(type);
+ }
- public static SkylarkType of(Class<?> type, Class<?> generic1) {
- return new SkylarkType(type, generic1);
+ /** @return the smallest java Class known to contain all elements of this type */
+ // Note: most user-code should be using a variant that throws an Exception
+ // if the result is Object.class but the type isn't TOP.
+ public Class<?> getType() {
+ return Object.class;
}
- public static SkylarkType of(Class<?> type) {
- if (type.equals(Object.class)) {
- return SkylarkType.UNKNOWN;
- } else if (type.equals(String.class)) {
- return SkylarkType.STRING;
- } else if (type.equals(Integer.class)) {
- return SkylarkType.INT;
- } else if (type.equals(Boolean.class)) {
- return SkylarkType.BOOL;
+ // The actual intersection function for users to use
+
+ public static SkylarkType intersection(SkylarkType t1, SkylarkType t2) {
+ if (t1.equals(t2)) {
+ return t1;
+ }
+ SkylarkType t = t1.intersectWith(t2);
+ if (t == BOTTOM) {
+ return t2.intersectWith(t1);
+ } else {
+ return t;
}
- return new SkylarkType(type);
}
- private SkylarkType(Class<?> type, Class<?> generic1) {
- this.type = Preconditions.checkNotNull(type);
- this.generic1 = Preconditions.checkNotNull(generic1);
+ public boolean includes(SkylarkType other) {
+ return intersection(this, other) == other;
}
- private SkylarkType(Class<?> type) {
- this.type = Preconditions.checkNotNull(type);
- this.generic1 = Object.class;
+ public boolean includes(Class<?> other) {
+ return includes(Simple.of(other));
}
- public Class<?> getType() {
- return type;
+ public SkylarkType getArgType() {
+ return TOP;
}
- Class<?> getGenericType1() {
- return generic1;
- }
+ private final class Empty { }; // Empty type, used as basis for Bottom
+
+ private static Map<Class<?>, Simple> simpleCache = // cache used by Simple
+ new HashMap<Class<?>, Simple>();
+
+ // Notable types
+
+ /** A singleton for the TOP type, that at analysis time means that any type is possible. */
+ public static final Top TOP = new Top();
+
+ /** UNKNOWN, an alias for the TOP type, for backward compatibility */
+ public static final Top UNKNOWN = TOP;
+ /** A singleton for the ANY type, that at run time means that any object is possible. */
+ // NB: right now, it has the same representation as TOP or UNKNOWN,
+ // but means something subtly different.
+ public static final SkylarkType ANY = Simple.of(Object.class);
+
+ /** A singleton for the BOTTOM type, that contains no element */
+ public static final Bottom BOTTOM = new Bottom();
+
+ /** NONE, the Unit type, isomorphic to Void, except its unique element prints as None */
+ // Note that we currently consider at validation time that None is in every type,
+ // by declaring its type as TOP instead of NONE, even though at runtime,
+ // we reject None from all types but NONE, and in particular from e.g. lists of Files.
+ // TODO(bazel-team): resolve this inconsistency, one way or the other.
+ public static final Simple NONE = Simple.of(Environment.NoneType.class);
+
+ private static final class Global {}
/**
- * Returns the stronger type of this and o if they are compatible. Stronger means that
- * the more information is available, e.g. STRING is stronger than UNKNOWN and
- * LIST&lt;STRING> is stronger than LIST&lt;UNKNOWN>. Note than there's no type
- * hierarchy in Skylark.
- * <p>If they are not compatible an EvalException is thrown.
+ * placeholder type for the namespace that global methods are relative to,
+ * used in the validation environment only.
*/
- SkylarkType infer(SkylarkType o, String name, Location thisLoc, Location originalLoc)
- throws EvalException {
- if (this == o) {
- return this;
+ public static final Simple GLOBAL = Simple.of(Global.class);
+
+ /** The STRING type, for strings */
+ public static final Simple STRING = Simple.of(String.class);
+
+ /** The INTEGER type, for 32-bit signed integers */
+ public static final Simple INT = Simple.of(Integer.class);
+
+ /** The BOOLEAN type, that contains TRUE and FALSE */
+ public static final Simple BOOL = Simple.of(Boolean.class);
+
+ /** The STRUCT type, for all Struct's */
+ public static final Simple STRUCT = Simple.of(ClassObject.class);
+
+ /** The FUNCTION type, that contains all functions, otherwise dynamically typed at call-time */
+ public static final SkylarkFunctionType FUNCTION = new SkylarkFunctionType("unknown", TOP);
+
+ /** The MAP type, that contains all Map's, and the generic combinator for maps */
+ public static final Simple MAP = Simple.of(Map.class);
+
+ /** The LIST type, that contains all SkylarkList's, and the generic combinator for them */
+ public static final Simple LIST = Simple.of(SkylarkList.class);
+
+ /** The STRING_LIST type, a SkylarkList of strings */
+ public static final SkylarkType STRING_LIST = Combination.of(LIST, STRING);
+
+ /** The INT_LIST type, a SkylarkList of integers */
+ public static final SkylarkType INT_LIST = Combination.of(LIST, INT);
+
+ /** The SET type, that contains all SkylarkList's, and the generic combinator for them */
+ public static final Simple SET = Simple.of(SkylarkNestedSet.class);
+
+
+ // Common subclasses of SkylarkType
+
+ /** the Top type contains all objects */
+ private static class Top extends Simple {
+ private Top() {
+ super(Object.class);
}
- if (this == UNKNOWN || this.equals(SkylarkType.NONE)) {
- return o;
+
+ @Override public boolean contains(Object value) {
+ return true;
}
- if (o == UNKNOWN || o.equals(SkylarkType.NONE)) {
- return this;
+ @Override public SkylarkType intersectWith(SkylarkType other) {
+ return other;
}
- if (!type.equals(o.type)) {
- throw new EvalException(thisLoc, String.format("bad %s: %s is incompatible with %s at %s",
- name,
- EvalUtils.getDataTypeNameFromClass(o.getType()),
- EvalUtils.getDataTypeNameFromClass(this.getType()),
- originalLoc));
+ @Override public String toString() {
+ return "Object";
}
- if (generic1.equals(Object.class)) {
- return o;
+ }
+
+ /** the Bottom type contains no element */
+ private static class Bottom extends Simple {
+ private Bottom() {
+ super(Empty.class);
}
- if (o.generic1.equals(Object.class)) {
+
+ @Override public SkylarkType intersectWith(SkylarkType other) {
return this;
}
- if (!generic1.equals(o.generic1)) {
- throw new EvalException(thisLoc, String.format("bad %s: incompatible generic variable types "
- + "%s with %s",
- name,
- EvalUtils.getDataTypeNameFromClass(o.generic1),
- EvalUtils.getDataTypeNameFromClass(this.generic1)));
+ @Override public String toString() {
+ return "EmptyType";
}
- return this;
}
- boolean isStruct() {
- return type.equals(ClassObject.class);
- }
+ /** a Simple type contains the instance of a Java class */
+ public static class Simple extends SkylarkType {
+ private final Class<?> type;
- boolean isList() {
- return SkylarkList.class.isAssignableFrom(type);
- }
+ public Simple(Class<?> type) {
+ this.type = type;
+ }
- boolean isDict() {
- return Map.class.isAssignableFrom(type);
+ @Override public boolean contains(Object value) {
+ return type.isAssignableFrom(value.getClass());
+ }
+ @Override public Class<?> getType() {
+ return type;
+ }
+ @Override public boolean equals(Object other) {
+ return this == other
+ || (this.getClass() == other.getClass() && this.type.equals(((Simple) other).getType()));
+ }
+ @Override public int hashCode() {
+ return 0x513973 + type.hashCode() * 503; // equal underlying types yield the same hashCode
+ }
+ @Override public String toString() {
+ return EvalUtils.getDataTypeNameFromClass(type);
+ }
+ @Override public boolean canBeCastTo(Class<?> type) {
+ return this.type == type || super.canBeCastTo(type);
+ }
+ public static Simple of(Class<?> type) {
+ Simple cached = simpleCache.get(type);
+ if (cached != null) {
+ return cached;
+ }
+ Simple simple;
+ if (type == Object.class) {
+ // Note that this is a bad encoding for "anything", not for "everything", i.e.
+ // for skylark there isn't a type that contains everything, but there's a Top type
+ // that corresponds to not knowing yet which more special type it will be.
+ 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 if (ClassObject.class != type && ClassObject.class.isAssignableFrom(type)) {
+ simple = of(ClassObject.class);
+ } else {
+ simple = new Simple(type);
+ }
+ simpleCache.put(type, simple);
+ return simple;
+ }
}
- boolean isSet() {
- return Set.class.isAssignableFrom(type);
- }
+ /** Combination of a generic type and an argument type */
+ public static class Combination extends SkylarkType {
+ // For the moment, we can only combine a Simple type with a Simple type,
+ // and the first one has to be a Java generic class,
+ // and in practice actually one of SkylarkList or SkylarkNestedSet
+ private final SkylarkType genericType; // actually always a Simple, for now.
+ private final SkylarkType argType; // not always Simple
+ public Combination(SkylarkType genericType, SkylarkType argType) {
+ this.genericType = genericType;
+ this.argType = argType;
+ }
- boolean isNset() {
- // TODO(bazel-team): NestedSets are going to be a bit strange with 2 type info (validation
- // and execution time). That can be cleaned up once we have complete type inference.
- return SkylarkNestedSet.class.isAssignableFrom(type);
- }
+ public boolean contains(Object value) {
+ // The empty collection is member of compatible types
+ if (!genericType.contains(value)) {
+ return false;
+ } else {
+ SkylarkType valueArgType = getGenericArgType(value);
+ return valueArgType == TOP // empty objects are universal
+ || argType.includes(valueArgType);
+ }
+ }
+ @Override public SkylarkType intersectWith(SkylarkType other) {
+ // For now, we only accept generics with a single covariant parameter
+ if (genericType.equals(other)) {
+ return this;
+ } else if (other instanceof Combination
+ && genericType.equals(((Combination) other).getGenericType())
+ && argType.includes(((Combination) other).getArgType())) {
+ return other;
+ } else if ((LIST.equals(other) || SET.equals(other)) && genericType.equals(other)) {
+ return this;
+ } else {
+ return BOTTOM;
+ }
+ }
- boolean isSimple() {
- return !isStruct() && !isDict() && !isList() && !isNset() && !isSet();
- }
+ @Override public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (this.getClass() == other.getClass()) {
+ Combination o = (Combination) other;
+ return genericType.equals(o.getGenericType())
+ && argType.equals(o.getArgType());
+ } else {
+ return false;
+ }
+ }
+ @Override public int hashCode() {
+ // equal underlying types yield the same hashCode
+ return 0x20B14A71 + genericType.hashCode() * 1009 + argType.hashCode() * 1013;
+ }
+ @Override public Class<?> getType() {
+ return genericType.getType();
+ }
+ SkylarkType getGenericType() {
+ return genericType;
+ }
+ @Override
+ public SkylarkType getArgType() {
+ return argType;
+ }
+ @Override public String toString() {
+ return genericType.toString() + " of " + argType.toString() + "s";
+ }
+
+ private static Interner<Combination> combinationInterner =
+ Interners.<Combination>newWeakInterner();
- @Override
- public String toString() {
- return this == UNKNOWN ? "Unknown" : EvalUtils.getDataTypeNameFromClass(type);
+ public static SkylarkType of(SkylarkType generic, SkylarkType argument) {
+ // assume all combinations with TOP are the same as the simple type, and canonicalize.
+ Preconditions.checkArgument(generic instanceof Simple);
+ if (argument == TOP) {
+ return generic;
+ } else {
+ return combinationInterner.intern(new Combination(generic, argument));
+ }
+ }
+ public static SkylarkType of(Class<?> generic, Class<?> argument) {
+ return of(Simple.of(generic), Simple.of(argument));
+ }
}
- // hashCode() and equals() only uses the type field
+ /** Union types, used a lot in "dynamic" languages such as Python or Skylark */
+ public static class Union extends SkylarkType {
+ private final ImmutableList<SkylarkType> types;
+ private Union(ImmutableList<SkylarkType> types) {
+ this.types = types;
+ }
- @Override
- public boolean equals(Object other) {
- if (this == other) {
- return true;
+ public boolean contains(Object value) {
+ for (SkylarkType type : types) {
+ if (type.contains(value)) {
+ return true;
+ }
+ }
+ return false;
}
- if (!(other instanceof SkylarkType)) {
+ @Override public boolean equals(Object other) {
+ if (this.getClass() == other.getClass()) {
+ Union o = (Union) other;
+ if (types.containsAll(o.types) && o.types.containsAll(types)) {
+ return true;
+ }
+ }
return false;
}
- SkylarkType o = (SkylarkType) other;
- return this.type.equals(o.type);
+ @Override public int hashCode() {
+ // equal underlying types yield the same hashCode
+ int h = 0x4104;
+ for (SkylarkType type : types) {
+ // Important: addition is commutative, like Union
+ h += type.hashCode();
+ }
+ return h;
+ }
+ @Override public String toString() {
+ return Joiner.on(" or ").join(types);
+ }
+ public static List<SkylarkType> addElements(List<SkylarkType> list, SkylarkType type) {
+ if (type instanceof Union) {
+ list.addAll(((Union) type).types);
+ } else if (type != BOTTOM) {
+ list.add(type);
+ }
+ return list;
+ }
+ @Override public SkylarkType intersectWith(SkylarkType other) {
+ List<SkylarkType> otherTypes = addElements(new ArrayList<SkylarkType>(), other);
+ List<SkylarkType> results = new ArrayList<>();
+ for (SkylarkType element : types) {
+ for (SkylarkType otherElement : otherTypes) {
+ addElements(results, intersection(element, otherElement));
+ }
+ }
+ return Union.of(results);
+ }
+ public static SkylarkType of(List<SkylarkType> types) {
+ // When making the union of many types,
+ // canonicalize them into elementary (non-Union) types,
+ // and then eliminate trivially redundant types from the list.
+
+ // list of all types in the input
+ ArrayList<SkylarkType> elements = new ArrayList<>();
+ for (SkylarkType type : types) {
+ addElements(elements, type);
+ }
+
+ // canonicalized list of types
+ ArrayList<SkylarkType> canonical = new ArrayList<>();
+
+ for (SkylarkType newType : elements) {
+ boolean done = false; // done with this element?
+ int i = 0;
+ for (SkylarkType existingType : canonical) {
+ SkylarkType both = intersection(newType, existingType);
+ if (newType.equals(both)) { // newType already included
+ done = true;
+ break;
+ } else if (existingType.equals(both)) { // newType supertype of existingType
+ canonical.set(i, newType);
+ done = true;
+ break;
+ }
+ }
+ if (!done) {
+ canonical.add(newType);
+ }
+ }
+ if (canonical.isEmpty()) {
+ return BOTTOM;
+ } else if (canonical.size() == 1) {
+ return canonical.get(0);
+ } else {
+ return new Union(ImmutableList.<SkylarkType>copyOf(canonical));
+ }
+ }
+ public static SkylarkType of(SkylarkType... types) {
+ return of(Arrays.asList(types));
+ }
+ public static SkylarkType of(SkylarkType t1, SkylarkType t2) {
+ return of(ImmutableList.<SkylarkType>of(t1, t2));
+ }
+ public static SkylarkType of(Class<?> t1, Class<?> t2) {
+ return of(Simple.of(t1), Simple.of(t2));
+ }
}
- @Override
- public int hashCode() {
- return type.hashCode();
+ public static SkylarkType of(Class<?> type) {
+ if (SkylarkList.class.isAssignableFrom(type)) {
+ return LIST;
+ } else if (SkylarkNestedSet.class.isAssignableFrom(type)) {
+ return SET;
+ } else {
+ return Simple.of(type);
+ }
+ }
+
+ public static SkylarkType of(SkylarkType t1, SkylarkType t2) {
+ return Combination.of(t1, t2);
}
+ public static SkylarkType of(Class<?> t1, Class<?> t2) {
+ return Combination.of(t1, t2);
+ }
+
/**
* A class representing the type of a Skylark function.
*/
+ // TODO(bazel-team): move the side-effect out of the type object, into the validation environment?
public static final class SkylarkFunctionType extends SkylarkType {
-
private final String name;
@Nullable private SkylarkType returnType;
@Nullable private Location returnTypeLoc;
+ @Override public SkylarkType intersectWith(SkylarkType other) {
+ // This gives the wrong result if both return types are incompatibly updated later!
+ if (other instanceof SkylarkFunctionType) {
+ SkylarkFunctionType fun = (SkylarkFunctionType) other;
+ SkylarkType type1 = returnType == null ? TOP : returnType;
+ SkylarkType type2 = fun.returnType == null ? TOP : fun.returnType;
+ SkylarkType bothReturnType = intersection(returnType, fun.returnType);
+ if (type1.equals(bothReturnType)) {
+ return this;
+ } else if (type2.equals(bothReturnType)) {
+ return fun;
+ } else {
+ return new SkylarkFunctionType(name, bothReturnType);
+ }
+ } else {
+ return BOTTOM;
+ }
+ }
+ @Override public Class<?> getType() {
+ return Function.class;
+ }
+ @Override public String toString() {
+ return (returnType == TOP || returnType == null ? "" : returnType + "-returning ")
+ + "function";
+ }
+
+ public boolean contains(Object value) {
+ // This returns true a bit too much, but it looks
+ return Function.class.isAssignableFrom(value.getClass());
+ }
+
public static SkylarkFunctionType of(String name) {
- return new SkylarkFunctionType(name, null);
+ return SkylarkFunctionType.of(name, TOP);
}
public static SkylarkFunctionType of(String name, SkylarkType returnType) {
@@ -192,7 +556,6 @@ public class SkylarkType {
}
private SkylarkFunctionType(String name, SkylarkType returnType) {
- super(Function.class);
this.name = name;
this.returnType = returnType;
}
@@ -203,26 +566,98 @@ public class SkylarkType {
/**
* Sets the return type of the function type if it's compatible with the existing return type.
- * Note that setting NONE only has an effect if the return type hasn't been set previously.
*/
public void setReturnType(SkylarkType newReturnType, Location newLoc) throws EvalException {
if (returnType == null) {
returnType = newReturnType;
returnTypeLoc = newLoc;
} else if (newReturnType != SkylarkType.NONE) {
- returnType =
+ // At validation-time, we allow NONE in any type, just like null in Java.
+ SkylarkType intersectionType =
returnType.infer(newReturnType, "return type of " + name, newLoc, returnTypeLoc);
- if (returnType == newReturnType) {
+ if (!returnType.equals(intersectionType)) {
+ returnType = intersectionType;
returnTypeLoc = newLoc;
}
}
}
}
+
+ // Utility functions regarding types
+
+ /**
+ * Returns the stronger type of this and o if they are compatible. Stronger means that
+ * the more information is available, e.g. STRING is stronger than UNKNOWN and
+ * LIST&lt;STRING> is stronger than LIST&lt;UNKNOWN>.
+ *
+ * <p>If they are not compatible an EvalException is thrown.
+ */
+ SkylarkType infer(SkylarkType o, String name, Location thisLoc, Location originalLoc)
+ throws EvalException {
+ SkylarkType both = intersection(this, o);
+ if (both == BOTTOM) {
+ throw new EvalException(thisLoc, String.format("bad %s: %s is incompatible with %s at %s",
+ name, o, this, originalLoc));
+ } else {
+ return both;
+ }
+ }
+
+ public static SkylarkType typeOf(Object value) {
+ if (value == null) {
+ return BOTTOM;
+ } else if (value instanceof SkylarkList) {
+ return of(LIST, ((SkylarkList) value).getContentType());
+ } else if (value instanceof SkylarkNestedSet) {
+ return of(SET, ((SkylarkNestedSet) value).getContentType());
+ } else if (value instanceof ClassObject) {
+ return STRUCT;
+ } else {
+ return Simple.of(value.getClass());
+ }
+ }
+
+ public static SkylarkType getGenericArgType(Object value) {
+ if (value instanceof SkylarkList) {
+ return ((SkylarkList) value).getContentType();
+ } else if (value instanceof SkylarkNestedSet) {
+ return ((SkylarkNestedSet) value).getContentType();
+ } else {
+ return TOP;
+ }
+ }
+
+ boolean isStruct() {
+ return ClassObject.class.isAssignableFrom(getType());
+ }
+
+ boolean isList() {
+ return SkylarkList.class.isAssignableFrom(getType());
+ }
+
+ boolean isDict() {
+ return Map.class.isAssignableFrom(getType());
+ }
+
+ boolean isSet() {
+ return Set.class.isAssignableFrom(getType());
+ }
+
+ boolean isNset() {
+ // TODO(bazel-team): NestedSets are going to be a bit strange with 2 type info (validation
+ // and execution time). That can be cleaned up once we have complete type inference.
+ return SkylarkNestedSet.class.isAssignableFrom(getType());
+ }
+
+ boolean isSimple() {
+ return !isStruct() && !isDict() && !isList() && !isNset() && !isSet();
+ }
+
private static boolean isTypeAllowedInSkylark(Object object) {
if (object instanceof NestedSet<?>) {
return false;
- } else if (object instanceof List<?>) {
+ } else if (object instanceof List<?> && !(object instanceof SkylarkList)) {
return false;
}
return true;
@@ -239,6 +674,44 @@ public class SkylarkType {
}
}
+ /**
+ * 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 cast(Object value, Class<T> type,
+ Location loc, String format, Object... args) throws EvalException {
+ try {
+ return type.cast(value);
+ } catch (ClassCastException e) {
+ throw new EvalException(loc, String.format(format, args));
+ }
+ }
+
+ /**
+ * General purpose type-casting facility.
+ *
+ * @param value - the actual value of the parameter
+ * @param genericType - a generic class of one argument for the value
+ * @param argType - a covariant argument for the generic class
+ * @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
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> T cast(Object value, Class<T> genericType, Class<?> argType,
+ Location loc, String format, Object... args) throws EvalException {
+ if (of(genericType, argType).contains(value)) {
+ return (T) value;
+ } else {
+ throw new EvalException(loc, String.format(format, args));
+ }
+ }
+
private static Class<?> getGenericTypeFromMethod(Method method) {
// This is where we can infer generic type information, so SkylarkNestedSets can be
// created in a safe way. Eventually we should probably do something with Lists and Maps too.
@@ -296,12 +769,10 @@ public class SkylarkType {
* Creates a SkylarkType from the SkylarkBuiltin annotation.
*/
public static SkylarkType getReturnType(SkylarkBuiltin annotation) {
- if (annotation.returnType().equals(Object.class)) {
- return SkylarkType.UNKNOWN;
- }
if (Function.class.isAssignableFrom(annotation.returnType())) {
return SkylarkFunctionType.of(annotation.name());
+ } else {
+ return Simple.of(annotation.returnType());
}
- return SkylarkType.of(annotation.returnType());
}
}