Refactor SkylarkList to allow MutableList
Make SkylarkList no longer read-only to match Python and the BUILD language.
Instead, subject it to a Mutability object inherited from the Environment.
--
MOS_MIGRATED_REVID=103332973
diff --git a/src/main/java/com/google/devtools/build/docgen/skylark/SkylarkDoc.java b/src/main/java/com/google/devtools/build/docgen/skylark/SkylarkDoc.java
index da77f73..a672165 100644
--- a/src/main/java/com/google/devtools/build/docgen/skylark/SkylarkDoc.java
+++ b/src/main/java/com/google/devtools/build/docgen/skylark/SkylarkDoc.java
@@ -17,6 +17,7 @@
import com.google.devtools.build.lib.syntax.FuncallExpression;
import com.google.devtools.build.lib.syntax.Runtime.NoneType;
import com.google.devtools.build.lib.syntax.SkylarkList;
+import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
import com.google.devtools.build.lib.syntax.SkylarkModule;
import com.google.devtools.build.lib.syntax.SkylarkSignature;
import com.google.devtools.build.lib.syntax.SkylarkSignature.Param;
@@ -54,6 +55,8 @@
return "<a class=\"anchor\" href=\"string.html\">string</a>";
} else if (Map.class.isAssignableFrom(type)) {
return "<a class=\"anchor\" href=\"dict.html\">dict</a>";
+ } else if (Tuple.class.isAssignableFrom(type)) {
+ return "<a class=\"anchor\" href=\"list.html\">tuple</a>";
} else if (List.class.isAssignableFrom(type) || SkylarkList.class.isAssignableFrom(type)
|| type == HackHackEitherList.class) {
// Annotated Java methods can return simple java.util.Lists (which get auto-converted).
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 b89cf72..7903587 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
@@ -381,8 +381,7 @@
return;
} else if (object instanceof SkylarkList) {
SkylarkList list = (SkylarkList) object;
- if (list == SkylarkList.EMPTY_LIST
- || isSimpleSkylarkObjectSafe(list.getContentType().getType())) {
+ if (list.isEmpty()) {
// Try not to iterate over the list if avoidable.
return;
}
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 2c7c706..0f95601 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
@@ -993,7 +993,7 @@
public List<Object> convert(Object x, String what, Object context)
throws ConversionException {
if (x instanceof SkylarkList) {
- return ((SkylarkList) x).toList();
+ return ((SkylarkList) x).getList();
} else if (x instanceof List) {
return (List<Object>) x;
} else if (x instanceof Iterable) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkCommandLine.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkCommandLine.java
index a262486..1a5319c 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkCommandLine.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkCommandLine.java
@@ -19,7 +19,8 @@
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.syntax.BuiltinFunction;
-import com.google.devtools.build.lib.syntax.SkylarkList;
+import com.google.devtools.build.lib.syntax.Environment;
+import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
import com.google.devtools.build.lib.syntax.SkylarkModule;
import com.google.devtools.build.lib.syntax.SkylarkNestedSet;
import com.google.devtools.build.lib.syntax.SkylarkSignature;
@@ -53,17 +54,19 @@
@SkylarkSignature(name = "template",
doc = "Transforms a set of files to a list of strings using the template string.",
objectType = SkylarkCommandLine.class,
- returnType = SkylarkList.class,
+ returnType = MutableList.class,
mandatoryPositionals = {
@Param(name = "items", type = SkylarkNestedSet.class, generic1 = Artifact.class,
doc = "The set of structs to transform."),
@Param(name = "template", type = String.class,
doc = "The template to use for the transformation, <code>%{path}</code> and "
+ "<code>%{short_path}</code> being substituted with the corresponding fields of each"
- + " file.")})
+ + " file.")},
+ useEnvironment = true)
private static BuiltinFunction template = new BuiltinFunction("template") {
- public SkylarkList invoke(final SkylarkNestedSet items, final String template) {
- return SkylarkList.lazyList(Iterables.transform(items, new Function<Object, String>() {
+ public MutableList invoke(final SkylarkNestedSet items, final String template,
+ Environment env) {
+ return new MutableList(Iterables.transform(items, new Function<Object, String>() {
@Override
public String apply(Object input) {
Artifact artifact = (Artifact) input;
@@ -71,7 +74,7 @@
.replace("%{path}", artifact.getExecPathString())
.replace("%{short_path}", artifact.getRootRelativePathString());
}
- }), String.class);
+ }), env);
}
};
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
index 5c13375..bc2bbe4 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleContext.java
@@ -31,7 +31,6 @@
import com.google.devtools.build.lib.analysis.MakeVariableExpander.ExpansionException;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.RuleContext;
-import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.analysis.config.FragmentCollection;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
@@ -50,7 +49,7 @@
import com.google.devtools.build.lib.syntax.Label;
import com.google.devtools.build.lib.syntax.Runtime;
import com.google.devtools.build.lib.syntax.SkylarkCallable;
-import com.google.devtools.build.lib.syntax.SkylarkList;
+import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
import com.google.devtools.build.lib.syntax.SkylarkModule;
import com.google.devtools.build.lib.syntax.SkylarkType;
import com.google.devtools.build.lib.vfs.PathFragment;
@@ -167,8 +166,7 @@
addOutput(outputsBuilder, attrName, Runtime.NONE);
}
} else if (type == Type.OUTPUT_LIST) {
- addOutput(outputsBuilder, attrName,
- SkylarkList.list(artifacts, Artifact.class));
+ addOutput(outputsBuilder, attrName, new MutableList(artifacts));
} else {
throw new IllegalArgumentException(
"Type of " + attrName + "(" + type + ") is not output type ");
@@ -227,7 +225,7 @@
attrBuilder.put(skyname, prereq);
} else {
// Type.LABEL_LIST
- attrBuilder.put(skyname, SkylarkList.list(allPrereq, TransitiveInfoCollection.class));
+ attrBuilder.put(skyname, new MutableList(allPrereq));
}
}
attrObject =
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java b/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
index f8ebe6b..43c25aa 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BaseFunction.java
@@ -22,6 +22,7 @@
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.packages.Type.ConversionException;
+import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
import java.util.ArrayList;
import java.util.HashMap;
@@ -231,11 +232,11 @@
// and this is actually the same as in Python.
int starParamIndex = numNamedParams;
if (numPositionalArgs > numPositionalParams) {
- arguments[starParamIndex] = SkylarkList.tuple(
- args.subList(numPositionalParams, numPositionalArgs));
+ arguments[starParamIndex] =
+ Tuple.copyOf(args.subList(numPositionalParams, numPositionalArgs));
numPositionalArgs = numPositionalParams; // clip numPositionalArgs
} else {
- arguments[starParamIndex] = SkylarkList.EMPTY_TUPLE;
+ arguments[starParamIndex] = Tuple.EMPTY;
}
} else if (numPositionalArgs > numPositionalParams) {
throw new EvalException(loc,
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 7ba3d24..cf98c53 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
@@ -15,8 +15,11 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
+import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
+import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
import java.util.Collection;
import java.util.Collections;
@@ -122,7 +125,7 @@
switch (operator) {
case PLUS:
- return plus(lval, rval);
+ return plus(lval, rval, env);
case PIPE:
if (lval instanceof SkylarkNestedSet) {
@@ -197,11 +200,8 @@
}
/* string % list: fall thru */
}
- if (rval instanceof SkylarkList) {
- SkylarkList rlist = (SkylarkList) rval;
- if (rlist.isTuple()) {
- return Printer.formatToString(pattern, rlist.toList());
- }
+ if (rval instanceof Tuple) {
+ return Printer.formatToString(pattern, ((Tuple) rval).getList());
}
return Printer.formatToString(pattern, Collections.singletonList(rval));
@@ -247,7 +247,7 @@
operator, EvalUtils.getDataTypeName(lval), EvalUtils.getDataTypeName(rval)));
}
- private Object plus(Object lval, Object rval) throws EvalException {
+ private Object plus(Object lval, Object rval, Environment env) throws EvalException {
// int + int
if (lval instanceof Integer && rval instanceof Integer) {
return ((Integer) lval).intValue() + ((Integer) rval).intValue();
@@ -264,8 +264,8 @@
List<?> rlist = (List<?>) rval;
if (EvalUtils.isTuple(llist) != EvalUtils.isTuple(rlist)) {
throw new EvalException(getLocation(), "can only concatenate "
- + EvalUtils.getDataTypeName(rlist) + " (not \"" + EvalUtils.getDataTypeName(llist)
- + "\") to " + EvalUtils.getDataTypeName(rlist));
+ + EvalUtils.getDataTypeName(llist) + " (not \"" + EvalUtils.getDataTypeName(rlist)
+ + "\") to " + EvalUtils.getDataTypeName(llist));
}
if (llist instanceof GlobList<?> || rlist instanceof GlobList<?>) {
return GlobList.concat(llist, rlist);
@@ -283,10 +283,21 @@
}
if ((lval instanceof SkylarkList || lval instanceof List<?>)
- && (rval instanceof SkylarkList || rval instanceof List<?>)) {
- SkylarkList left = (SkylarkList) SkylarkType.convertToSkylark(lval, getLocation());
- SkylarkList right = (SkylarkList) SkylarkType.convertToSkylark(rval, getLocation());
- return SkylarkList.concat(left, right, getLocation());
+ && ((rval instanceof SkylarkList || rval instanceof List<?>))) {
+ SkylarkList left = (SkylarkList) SkylarkType.convertToSkylark(lval, env);
+ SkylarkList right = (SkylarkList) SkylarkType.convertToSkylark(rval, env);
+ boolean isImmutable = left.isTuple();
+ if (isImmutable != right.isTuple()) {
+ throw new EvalException(getLocation(), "can only concatenate "
+ + EvalUtils.getDataTypeName(left) + " (not \"" + EvalUtils.getDataTypeName(right)
+ + "\") to " + EvalUtils.getDataTypeName(left));
+ }
+ Iterable<Object> concatenated = Iterables.concat(left, right);
+ if (isImmutable) {
+ return Tuple.copyOf(concatenated);
+ } else {
+ return new MutableList(concatenated, env);
+ }
}
if (lval instanceof Map<?, ?> && rval instanceof Map<?, ?>) {
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
index f930cb3..3c7333b 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
@@ -51,7 +51,7 @@
Object doEval(Environment env) throws EvalException, InterruptedException {
Object objValue = obj.eval(env);
String name = field.getName();
- Object result = eval(objValue, name, getLocation());
+ Object result = eval(objValue, name, getLocation(), env);
if (result == null) {
if (objValue instanceof ClassObject) {
String customErrorMessage = ((ClassObject) objValue).errorMessage(name);
@@ -68,7 +68,8 @@
/**
* Returns the field of the given name of the struct objValue, or null if no such field exists.
*/
- public static Object eval(Object objValue, String name, Location loc) throws EvalException {
+ public static Object eval(Object objValue, String name,
+ Location loc, Environment env) throws EvalException {
if (objValue instanceof ClassObject) {
Object result = null;
try {
@@ -79,7 +80,7 @@
// ClassObjects may have fields that are annotated with @SkylarkCallable.
// Since getValue() does not know about those, we cannot expect that result is a valid object.
if (result != null) {
- result = SkylarkType.convertToSkylark(result, loc);
+ result = SkylarkType.convertToSkylark(result, env);
// If we access NestedSets using ClassObject.getValue() we won't know the generic type,
// so we have to disable it. This should not happen.
SkylarkType.checkTypeAllowedInSkylark(result, loc);
@@ -92,7 +93,7 @@
if (methods != null && !methods.isEmpty()) {
MethodDescriptor method = Iterables.getOnlyElement(methods);
if (method.getAnnotation().structField()) {
- return FuncallExpression.callMethod(method, name, objValue, new Object[] {}, loc);
+ return FuncallExpression.callMethod(method, name, objValue, new Object[] {}, loc, env);
}
}
return null;
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 dd59ec9..eb26e38 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
@@ -68,15 +68,11 @@
@Override
@SuppressWarnings("unchecked")
public int compare(Object o1, Object o2) {
- Location loc = null;
- try {
- o1 = SkylarkType.convertToSkylark(o1, loc);
- o2 = SkylarkType.convertToSkylark(o2, loc);
- } catch (EvalException e) {
- throw new ComparisonException(e.getMessage());
- }
+ o1 = SkylarkType.convertToSkylark(o1, /*env=*/ null);
+ o2 = SkylarkType.convertToSkylark(o2, /*env=*/ null);
- if (o1 instanceof SkylarkList && o2 instanceof SkylarkList) {
+ if (o1 instanceof SkylarkList && o2 instanceof SkylarkList
+ && ((SkylarkList) o1).isTuple() == ((SkylarkList) o2).isTuple()) {
return compareLists((SkylarkList) o1, (SkylarkList) o2);
}
try {
@@ -225,8 +221,6 @@
return ImmutableList.class;
} else if (List.class.isAssignableFrom(c)) {
return List.class;
- } else if (SkylarkList.class.isAssignableFrom(c)) {
- return SkylarkList.class;
} else if (Map.class.isAssignableFrom(c)) {
return Map.class;
} else if (NestedSet.class.isAssignableFrom(c)) {
@@ -266,7 +260,7 @@
if (list.isTuple()) {
return "tuple";
} else {
- return "list" + (full ? " of " + list.getContentType() + "s" : "");
+ return "list";
}
} else if (object instanceof SkylarkNestedSet) {
SkylarkNestedSet set = (SkylarkNestedSet) object;
@@ -289,7 +283,11 @@
* when the given class identifies a Skylark name space.
*/
public static String getDataTypeNameFromClass(Class<?> c, boolean highlightNameSpaces) {
- if (c.equals(Object.class)) {
+ if (c.isAnnotationPresent(SkylarkModule.class)) {
+ SkylarkModule module = c.getAnnotation(SkylarkModule.class);
+ return c.getAnnotation(SkylarkModule.class).name()
+ + ((module.namespace() && highlightNameSpaces) ? " (a language module)" : "");
+ } else if (c.equals(Object.class)) {
return "unknown";
} else if (c.equals(String.class)) {
return "string";
@@ -297,13 +295,10 @@
return "int";
} else if (c.equals(Boolean.class)) {
return "bool";
- } else if (c.equals(Void.TYPE) || c.equals(Runtime.NoneType.class)) {
- // TODO(bazel-team): no one should be seeing Void at all.
- return "NoneType";
} else if (List.class.isAssignableFrom(c)) {
- // NB: the capital here is a subtle way to distinguish java Tuple and java List
- // from native SkylarkList tuple and list.
- // TODO(bazel-team): refactor SkylarkList and use it everywhere.
+ // NB: the capital here is a subtle way to distinguish java List and Tuple (ImmutableList)
+ // from native SkylarkList list and tuple.
+ // TODO(bazel-team): use SkylarkList everywhere instead of java List.
return isTuple(c) ? "Tuple" : "List";
} else if (Map.class.isAssignableFrom(c)) {
return "dict";
@@ -316,13 +311,6 @@
return "set";
} else if (ClassObject.SkylarkClassObject.class.isAssignableFrom(c)) {
return "struct";
- } else if (SkylarkList.class.isAssignableFrom(c)) {
- // TODO(bazel-team): Refactor the class hierarchy so we can distinguish list and tuple types.
- return "list";
- } else if (c.isAnnotationPresent(SkylarkModule.class)) {
- SkylarkModule module = c.getAnnotation(SkylarkModule.class);
- return c.getAnnotation(SkylarkModule.class).name()
- + ((module.namespace() && highlightNameSpaces) ? " (a language module)" : "");
} else {
if (c.getSimpleName().isEmpty()) {
return c.getName();
@@ -381,7 +369,7 @@
if (o instanceof Collection) {
return (Collection<Object>) o;
} else if (o instanceof SkylarkList) {
- return ((SkylarkList) o).toList();
+ return ((SkylarkList) o).getList();
} else if (o instanceof Map<?, ?>) {
Map<Comparable<?>, Object> dict = (Map<Comparable<?>, Object>) o;
// For dictionaries we iterate through the keys only
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 cf16b68..4f26277 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
@@ -318,7 +318,7 @@
}
static Object callMethod(MethodDescriptor methodDescriptor, String methodName, Object obj,
- Object[] args, Location loc) throws EvalException {
+ Object[] args, Location loc, Environment env) throws EvalException {
try {
Method method = methodDescriptor.getMethod();
if (obj == null && !Modifier.isStatic(method.getModifiers())) {
@@ -340,7 +340,7 @@
+ Printer.listString(ImmutableList.copyOf(args), "(", ", ", ")", null));
}
}
- result = SkylarkType.convertToSkylark(result, method);
+ result = SkylarkType.convertToSkylark(result, method, env);
if (result != null && !EvalUtils.isSkylarkAcceptable(result.getClass())) {
throw new EvalException(loc, Printer.format(
"Method '%s' returns an object of invalid type %r", methodName, result.getClass()));
@@ -464,7 +464,7 @@
value = SkylarkType.convertFromSkylark(value);
} else if (conversion == ArgConversion.TO_SKYLARK) {
// We try to auto convert the type if we can.
- value = SkylarkType.convertToSkylark(value, getLocation());
+ value = SkylarkType.convertToSkylark(value, env);
// We call into Skylark so we need to be sure that the caller uses the appropriate types.
SkylarkType.checkTypeAllowedInSkylark(value, getLocation());
} // else NO_CONVERSION
@@ -565,7 +565,7 @@
+ "\nwhile calling method '%s' for type %s",
name, EvalUtils.getDataTypeNameFromClass(objClass)));
}
- return callMethod(method, name, obj, args.toArray(), getLocation());
+ return callMethod(method, name, obj, args.toArray(), getLocation(), env);
} else {
throw new EvalException(
getLocation(),
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 144d002..6460bbc 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
@@ -15,6 +15,7 @@
package com.google.devtools.build.lib.syntax;
import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
import java.io.Serializable;
import java.util.ArrayList;
@@ -219,7 +220,7 @@
Object doEval(Environment env) throws EvalException, InterruptedException {
List<Object> result = new ArrayList<>();
evalStep(env, result, 0);
- return env.isSkylark() ? SkylarkList.list(result, getLocation()) : result;
+ return env.isSkylark() ? new MutableList(result, env) : result;
}
@Override
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 5e33204..36f4829 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
@@ -13,6 +13,9 @@
// limitations under the License.
package com.google.devtools.build.lib.syntax;
+import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
+import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -108,8 +111,7 @@
result.add(expr.eval(env));
}
if (env.isSkylark()) {
- return isTuple()
- ? SkylarkList.tuple(result) : SkylarkList.list(result, getLocation());
+ return isTuple() ? Tuple.copyOf(result) : new MutableList(result, env);
} else {
return EvalUtils.makeSequence(result, isTuple());
}
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 18ed1c7..a6ef07d 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
@@ -25,6 +25,8 @@
import com.google.devtools.build.lib.packages.Type;
import com.google.devtools.build.lib.packages.Type.ConversionException;
import com.google.devtools.build.lib.syntax.ClassObject.SkylarkClassObject;
+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.SkylarkSignature.Param;
import com.google.devtools.build.lib.syntax.SkylarkSignatureProcessor.HackHackEitherList;
@@ -190,7 +192,7 @@
maxSplitO, "'split' argument of 'split'", /*label*/null, -2);
// + 1 because the last result is the remainder, and default of -2 so that after +1 it's -1
String[] ss = Pattern.compile(sep, Pattern.LITERAL).split(self, maxSplit + 1);
- return convert(Arrays.<String>asList(ss), env, loc);
+ return convert(Arrays.<String>asList(ss), env);
}
};
@@ -221,7 +223,7 @@
throw new EvalException(loc, ex);
}
- return convert(result, env, loc);
+ return convert(result, env);
}
};
@@ -327,7 +329,7 @@
private static Object partitionWrapper(String self, String separator, boolean forward,
Environment env, Location loc) throws EvalException {
try {
- return convert(stringPartition(self, separator, forward), env, loc);
+ return convert(stringPartition(self, separator, forward), env);
} catch (IllegalArgumentException ex) {
throw new EvalException(loc, ex);
}
@@ -604,8 +606,7 @@
doc = "Test beginning at this position."),
@Param(name = "end", type = Integer.class, noneable = true, defaultValue = "None",
doc = "optional position at which to stop comparing.")})
- private static BuiltinFunction endswith = new BuiltinFunction(
- "endswith", SkylarkList.tuple(0, Runtime.NONE)) {
+ private static BuiltinFunction endswith = new BuiltinFunction("endswith") {
public Boolean invoke(String self, String sub, Integer start, Object end)
throws ConversionException {
return pythonSubstring(self, start, end, "'end' operand of 'endswith'").endsWith(sub);
@@ -708,20 +709,35 @@
}
};
- @SkylarkSignature(name = "$slice", objectType = SkylarkList.class,
+ @SkylarkSignature(name = "$slice", objectType = MutableList.class, returnType = MutableList.class,
documented = false,
mandatoryPositionals = {
- @Param(name = "self", type = SkylarkList.class, doc = "This list or tuple."),
+ @Param(name = "self", type = MutableList.class, doc = "This list."),
@Param(name = "start", type = Integer.class, doc = "start position of the slice."),
@Param(name = "end", type = Integer.class, doc = "end position of the slice.")},
doc = "x[<code>start</code>:<code>end</code>] returns a slice or a list slice.",
- useLocation = true)
- private static BuiltinFunction skylarkListSlice = new BuiltinFunction("$slice") {
- public Object invoke(SkylarkList self, Integer left, Integer right,
- Location loc) throws EvalException, ConversionException {
- return SkylarkList.list(sliceList(self.toList(), left, right), loc);
- }
- };
+ useEnvironment = true)
+ private static BuiltinFunction mutableListSlice = new BuiltinFunction("$slice") {
+ public MutableList invoke(MutableList self, Integer left, Integer right,
+ Environment env) throws EvalException, ConversionException {
+ return new MutableList(sliceList(self.getList(), left, right), env);
+ }
+ };
+
+ @SkylarkSignature(name = "$slice", objectType = Tuple.class, returnType = Tuple.class,
+ documented = false,
+ mandatoryPositionals = {
+ @Param(name = "self", type = Tuple.class, doc = "This tuple."),
+ @Param(name = "start", type = Integer.class, doc = "start position of the slice."),
+ @Param(name = "end", type = Integer.class, doc = "end position of the slice.")},
+ doc = "x[<code>start</code>:<code>end</code>] returns a slice or a list slice.",
+ useEnvironment = true)
+ private static BuiltinFunction tupleSlice = new BuiltinFunction("$slice") {
+ public Tuple invoke(Tuple self, Integer left, Integer right,
+ Environment env) throws EvalException, ConversionException {
+ return Tuple.copyOf(sliceList(self.getList(), left, right));
+ }
+ };
private static List<Object> sliceList(List<Object> list, Integer left, Integer right) {
left = clampIndex(left, list.size());
@@ -751,7 +767,7 @@
Collection<?> col =
Ordering.from(EvalUtils.SKYLARK_COMPARATOR)
.sortedCopy(EvalUtils.toCollection(self, loc));
- return convert(col, env, loc);
+ return convert(col, env);
} catch (EvalUtils.ComparisonException e) {
throw new EvalException(loc, e);
}
@@ -776,10 +792,6 @@
new BuiltinFunction("append") {
public Runtime.NoneType invoke(List<Object> self, Object item,
Location loc, Environment env) throws EvalException, ConversionException {
- if (env.isCallerSkylark()) {
- throw new EvalException(loc,
- "function 'append' is not available in Skylark");
- }
if (EvalUtils.isTuple(self)) {
throw new EvalException(loc,
"function 'append' is not defined on object of type 'Tuple'");
@@ -789,6 +801,27 @@
}
};
+ @SkylarkSignature(
+ name = "append",
+ objectType = MutableList.class,
+ returnType = Runtime.NoneType.class,
+ documented = false,
+ doc = "Adds an item to the end of the list.",
+ mandatoryPositionals = {
+ @Param(name = "self", type = MutableList.class, doc = "This list."),
+ @Param(name = "item", type = Object.class, doc = "Item to add at the end.")
+ },
+ useLocation = true,
+ useEnvironment = true)
+ private static BuiltinFunction skylarkAppend =
+ new BuiltinFunction("append") {
+ public Runtime.NoneType invoke(MutableList self, Object item,
+ Location loc, Environment env) throws EvalException, ConversionException {
+ self.add(item, loc, env);
+ return Runtime.NONE;
+ }
+ };
+
// This function has a SkylarkSignature but is only used by the Build language, not Skylark.
@SkylarkSignature(
name = "extend",
@@ -802,14 +835,10 @@
@Param(name = "items", type = List.class, doc = "Items to add at the end.")},
useLocation = true,
useEnvironment = true)
- private static BuiltinFunction extend =
+ private static BuiltinFunction skylarkExtend =
new BuiltinFunction("extend") {
public Runtime.NoneType invoke(List<Object> self, List<Object> items,
Location loc, Environment env) throws EvalException, ConversionException {
- if (env.isCallerSkylark()) {
- throw new EvalException(loc,
- "function 'append' is not available in Skylark");
- }
if (EvalUtils.isTuple(self)) {
throw new EvalException(loc,
"function 'extend' is not defined on object of type 'Tuple'");
@@ -819,6 +848,26 @@
}
};
+ @SkylarkSignature(
+ name = "extend",
+ objectType = MutableList.class,
+ returnType = Runtime.NoneType.class,
+ documented = false,
+ doc = "Adds all items to the end of the list.",
+ mandatoryPositionals = {
+ @Param(name = "self", type = MutableList.class, doc = "This list."),
+ @Param(name = "items", type = SkylarkList.class, doc = "Items to add at the end.")},
+ useLocation = true,
+ useEnvironment = true)
+ private static BuiltinFunction extend =
+ new BuiltinFunction("extend") {
+ public Runtime.NoneType invoke(MutableList self, SkylarkList items,
+ Location loc, Environment env) throws EvalException, ConversionException {
+ self.addAll(items, loc, env);
+ return Runtime.NONE;
+ }
+ };
+
// dictionary access operator
@SkylarkSignature(name = "$index", documented = false, objectType = Map.class,
doc = "Looks up a value in a dictionary.",
@@ -837,23 +886,40 @@
};
// list access operator
- @SkylarkSignature(name = "$index", documented = false, objectType = SkylarkList.class,
+ @SkylarkSignature(name = "$index", documented = false, objectType = MutableList.class,
doc = "Returns the nth element of a list.",
mandatoryPositionals = {
- @Param(name = "self", type = SkylarkList.class, doc = "This object."),
+ @Param(name = "self", type = MutableList.class, doc = "This list."),
@Param(name = "key", type = Object.class, doc = "The index or key to access.")},
useLocation = true)
- private static BuiltinFunction skylarkListIndexOperator = new BuiltinFunction("$index") {
- public Object invoke(SkylarkList self, Object key,
- Location loc) throws EvalException, ConversionException {
- if (self.isEmpty()) {
- throw new EvalException(loc, "List is empty");
+ private static BuiltinFunction mutableListIndexOperator = new BuiltinFunction("$index") {
+ public Object invoke(MutableList self, Object key,
+ Location loc) throws EvalException, ConversionException {
+ if (self.isEmpty()) {
+ throw new EvalException(loc, "List is empty");
+ }
+ int index = getListIndex(key, self.size(), loc);
+ return self.getList().get(index);
}
- int index = getListIndex(key, self.size(), loc);
- return self.get(index);
- }
- };
+ };
+ // tuple access operator
+ @SkylarkSignature(name = "$index", documented = false, objectType = Tuple.class,
+ doc = "Returns the nth element of a list.",
+ mandatoryPositionals = {
+ @Param(name = "self", type = Tuple.class, doc = "This tuple."),
+ @Param(name = "key", type = Object.class, doc = "The index or key to access.")},
+ useLocation = true)
+ private static BuiltinFunction tupleIndexOperator = new BuiltinFunction("$index") {
+ public Object invoke(Tuple self, Object key,
+ Location loc) throws EvalException, ConversionException {
+ if (self.isEmpty()) {
+ throw new EvalException(loc, "Tuple is empty");
+ }
+ int index = getListIndex(key, self.size(), loc);
+ return self.getList().get(index);
+ }
+ };
// list access operator
@SkylarkSignature(name = "$index", documented = false, objectType = List.class,
@@ -899,7 +965,7 @@
Location loc, Environment env) throws EvalException, ConversionException {
// Use a TreeMap to ensure consistent ordering.
Map<?, ?> dict = new TreeMap<>(self);
- return convert(dict.values(), env, loc);
+ return convert(dict.values(), env);
}
};
@@ -920,9 +986,9 @@
List<Object> list = Lists.newArrayListWithCapacity(dict.size());
for (Map.Entry<?, ?> entries : dict.entrySet()) {
List<?> item = ImmutableList.of(entries.getKey(), entries.getValue());
- list.add(env.isCallerSkylark() ? SkylarkList.tuple(item) : item);
+ list.add(env.isCallerSkylark() ? Tuple.copyOf(item) : item);
}
- return convert(list, env, loc);
+ return convert(list, env);
}
};
@@ -939,7 +1005,7 @@
private static BuiltinFunction keys = new BuiltinFunction("keys") {
public Object invoke(Map<Comparable<?>, ?> dict,
Location loc, Environment env) throws EvalException {
- return convert(Ordering.natural().sortedCopy(dict.keySet()), env, loc);
+ return convert(Ordering.natural().sortedCopy(dict.keySet()), env);
}
};
@@ -964,10 +1030,10 @@
// TODO(bazel-team): Use the same type for both Skylark and BUILD files.
@SuppressWarnings("unchecked")
- private static Iterable<Object> convert(Collection<?> list, Environment env, Location loc)
+ private static Iterable<Object> convert(Collection<?> list, Environment env)
throws EvalException {
if (env.isCallerSkylark()) {
- return SkylarkList.list(list, loc);
+ return new MutableList(list, env);
} else {
return Lists.newArrayList(list);
}
@@ -985,16 +1051,16 @@
}
};
- @SkylarkSignature(name = "list", returnType = SkylarkList.class,
+ @SkylarkSignature(name = "list", returnType = MutableList.class,
doc = "Converts a collection (e.g. set or dictionary) to a list."
+ "<pre class=\"language-python\">list([1, 2]) == [1, 2]\n"
+ "list(set([2, 3, 2])) == [2, 3]\n"
+ "list({5: \"a\", 2: \"b\", 4: \"c\"}) == [2, 4, 5]</pre>",
mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")},
- useLocation = true)
+ useLocation = true, useEnvironment = true)
private static BuiltinFunction list = new BuiltinFunction("list") {
- public SkylarkList invoke(Object x, Location loc) throws EvalException {
- return SkylarkList.list(EvalUtils.toCollection(x, loc), loc);
+ public MutableList invoke(Object x, Location loc, Environment env) throws EvalException {
+ return new MutableList(EvalUtils.toCollection(x, loc), env);
}
};
@@ -1187,10 +1253,10 @@
int count = 0;
List<SkylarkList> result = Lists.newArrayList();
for (Object obj : Type.OBJECT_LIST.convert(input, "input")) {
- result.add(SkylarkList.tuple(count, obj));
+ result.add(Tuple.of(count, obj));
count++;
}
- return convert(result, env, loc);
+ return convert(result, env);
}
};
@@ -1242,7 +1308,7 @@
start += step;
}
}
- return convert(result, env, loc);
+ return convert(result, env);
}
};
@@ -1303,11 +1369,11 @@
@Param(name = "default", defaultValue = "None",
doc = "The default value to return in case the struct "
+ "doesn't have an attribute of the given name.")},
- useLocation = true)
+ useLocation = true, useEnvironment = true)
private static final BuiltinFunction getattr = new BuiltinFunction("getattr") {
public Object invoke(Object obj, String name, Object defaultValue,
- Location loc) throws EvalException, ConversionException {
- Object result = DotExpression.eval(obj, name, loc);
+ Location loc, Environment env) throws EvalException, ConversionException {
+ Object result = DotExpression.eval(obj, name, loc, env);
if (result == null) {
if (defaultValue != Runtime.NONE) {
return defaultValue;
@@ -1340,7 +1406,7 @@
// This shouldn't happen
throw new EvalException(loc, e.getMessage());
}
- return SkylarkList.list(fields, String.class);
+ return new MutableList(fields, env);
}
};
@@ -1409,15 +1475,15 @@
+ "zip([1, 2], [3, 4]) # == [(1, 3), (2, 4)]\n"
+ "zip([1, 2], [3, 4, 5]) # == [(1, 3), (2, 4)]</pre>",
extraPositionals = {@Param(name = "args", doc = "lists to zip")},
- returnType = SkylarkList.class, useLocation = true)
+ returnType = MutableList.class, useLocation = true, useEnvironment = true)
private static final BuiltinFunction zip = new BuiltinFunction("zip") {
- public SkylarkList invoke(SkylarkList args, Location loc)
+ public MutableList invoke(SkylarkList args, Location loc, Environment env)
throws EvalException {
Iterator<?>[] iterators = new Iterator<?>[args.size()];
for (int i = 0; i < args.size(); i++) {
iterators[i] = EvalUtils.toIterable(args.get(i), loc).iterator();
}
- List<SkylarkList> result = new ArrayList<SkylarkList>();
+ List<Tuple> result = new ArrayList<>();
boolean allHasNext;
do {
allHasNext = !args.isEmpty();
@@ -1430,10 +1496,10 @@
}
}
if (allHasNext) {
- result.add(SkylarkList.tuple(elem));
+ result.add(Tuple.copyOf(elem));
}
} while (allHasNext);
- return SkylarkList.list(result, loc);
+ return new MutableList(result, env);
}
};
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Mutability.java b/src/main/java/com/google/devtools/build/lib/syntax/Mutability.java
index a742b7f..31ec345 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Mutability.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Mutability.java
@@ -93,6 +93,15 @@
}
/**
+ * Freezes this Mutability
+ * @return it in fluent style.
+ */
+ public Mutability freeze() {
+ close();
+ return this;
+ }
+
+ /**
* A MutabilityException will be thrown when the user attempts to mutate an object he shouldn't.
*/
static class MutabilityException extends Exception {
@@ -137,4 +146,6 @@
throw new AssertionError("trying to mutate an object from a different context");
}
}
+
+ public static final Mutability IMMUTABLE = create("IMMUTABLE").freeze();
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Printer.java b/src/main/java/com/google/devtools/build/lib/syntax/Printer.java
index bc00795..b4af645 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Printer.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Printer.java
@@ -15,6 +15,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.io.IOException;
@@ -427,7 +428,7 @@
if (a >= argLength) {
throw new MissingFormatWidthException("not enough arguments for format pattern "
+ repr(pattern) + ": "
- + repr(SkylarkList.tuple(arguments)));
+ + repr(Tuple.copyOf(arguments)));
}
Object argument = arguments.get(a++);
switch (directive) {
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 098836a..ca83d65 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
@@ -42,6 +42,8 @@
/**
* There should be only one instance of this type to allow "== None" tests.
*/
+ @SkylarkModule(name = "NoneType", documented = false,
+ doc = "Unit type, containing the unique value None")
@Immutable
public static final class NoneType implements SkylarkValue {
private NoneType() {}
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 282a7af..2da6f78 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
@@ -14,68 +14,72 @@
package com.google.devtools.build.lib.syntax;
-import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.syntax.Mutability.Freezable;
+import com.google.devtools.build.lib.syntax.Mutability.MutabilityException;
-import java.util.Collection;
+import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import javax.annotation.Nullable;
+
/**
* A class to handle lists and tuples in Skylark.
*/
-@SkylarkModule(name = "list",
- doc = "A language built-in type to support lists. Example of list literal:<br>"
- + "<pre class=language-python>x = [1, 2, 3]</pre>"
- + "Accessing elements is possible using indexing (starts from <code>0</code>):<br>"
- + "<pre class=language-python>e = x[1] # e == 2</pre>"
- + "Lists support the <code>+</code> operator to concatenate two lists. Example:<br>"
- + "<pre class=language-python>x = [1, 2] + [3, 4] # x == [1, 2, 3, 4]\n"
- + "x = [\"a\", \"b\"]\n"
- + "x += [\"c\"] # x == [\"a\", \"b\", \"c\"]</pre>"
- + "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 have it implement List<Object> like ImmutableList does?
+@SkylarkModule(name = "sequence", documented = false,
+ doc = "common type of lists and tuples")
public abstract class SkylarkList implements Iterable<Object>, SkylarkValue {
- private final boolean tuple;
- private final SkylarkType contentType;
-
- private SkylarkList(boolean tuple, SkylarkType contentType) {
- this.tuple = tuple;
- this.contentType = contentType;
- }
+ /**
+ * Returns the List object underlying this SkylarkList.
+ * Mutating it (if mutable) will actually mutate the contents of the list.
+ */
+ // TODO(bazel-team): make this public no more.
+ public abstract List<Object> getList();
/**
- * The size of the list.
+ * Returns an ImmutableList object with the current underlying contents of this SkylarkList.
*/
- public abstract int size();
-
- /**
- * Returns true if the list is empty.
- */
- public abstract boolean isEmpty();
-
- /**
- * Returns the i-th element of the list.
- */
- public abstract Object get(int i);
+ public abstract ImmutableList<Object> getImmutableList();
/**
* Returns true if this list is a tuple.
*/
- public boolean isTuple() {
- return tuple;
+ public abstract boolean isTuple();
+
+ /**
+ * The size of the list.
+ */
+ public final int size() {
+ return getList().size();
}
- @VisibleForTesting
- public SkylarkType getContentType() {
- return contentType;
+ /**
+ * Returns true if the list is empty.
+ */
+ public final boolean isEmpty() {
+ return getList().isEmpty();
+ }
+
+ /**
+ * Returns the i-th element of the list.
+ */
+ public final Object get(int i) {
+ return getList().get(i);
+ }
+
+ @Override
+ public void write(Appendable buffer, char quotationMark) {
+ Printer.printList(buffer, getList(), isTuple(), quotationMark);
+ }
+
+ @Override
+ public final Iterator<Object> iterator() {
+ return getList().iterator();
}
@Override
@@ -85,349 +89,233 @@
@Override
public boolean equals(Object object) {
- if (this == object) {
- return true;
- }
- if (!(object instanceof SkylarkList)) {
- return false;
- }
- SkylarkList other = (SkylarkList) object;
- if (this.isTuple() != other.isTuple()) {
- return false;
- }
- return toList().equals(other.toList());
+ return (this == object)
+ || ((this.getClass() == object.getClass())
+ && getList().equals(((SkylarkList) object).getList()));
}
@Override
public int hashCode() {
- return SkylarkList.class.hashCode()
- + Boolean.valueOf(isTuple()).hashCode()
- + 31 * toList().hashCode();
+ return getClass().hashCode() + 31 * getList().hashCode();
}
- // TODO(bazel-team): we should be very careful using this method. Check and remove
- // auto conversions on the Java-Skylark interface if possible.
- /**
- * Returns a mutable Java list copy of this SkylarkList if it's a list or an
- * immutable copy if it's a tuple.
- */
- public abstract List<Object> toList();
-
@SuppressWarnings("unchecked")
public <T> Iterable<T> to(Class<T> type) {
- Preconditions.checkArgument(this == EMPTY_LIST || contentType.canBeCastTo(type));
+ for (Object value : getList()) {
+ Preconditions.checkArgument(
+ type.isInstance(value),
+ Printer.formattable("list element %r is not of type %r", value, type));
+ }
return (Iterable<T>) this;
}
- private static final class EmptySkylarkList extends SkylarkList {
- private EmptySkylarkList(boolean tuple) {
- super(tuple, SkylarkType.TOP);
+ /**
+ * A class for mutable lists.
+ */
+ @SkylarkModule(name = "list",
+ doc = "A language built-in type to support lists. Example of list literal:<br>"
+ + "<pre class=language-python>x = [1, 2, 3]</pre>"
+ + "Accessing elements is possible using indexing (starts from <code>0</code>):<br>"
+ + "<pre class=language-python>e = x[1] # e == 2</pre>"
+ + "Lists support the <code>+</code> operator to concatenate two lists. Example:<br>"
+ + "<pre class=language-python>x = [1, 2] + [3, 4] # x == [1, 2, 3, 4]\n"
+ + "x = [\"a\", \"b\"]\n"
+ + "x += [\"c\"] # x == [\"a\", \"b\", \"c\"]</pre>"
+ + "Lists are mutable, as in Python.")
+ public static final class MutableList extends SkylarkList implements Freezable {
+
+ private final ArrayList<Object> contents = new ArrayList<>();
+
+ private final Mutability mutability;
+
+ /**
+ * Creates a MutableList from contents and a Mutability.
+ * @param contents the contents of the list
+ * @param mutability a Mutability context
+ * @return a MutableList containing the elements
+ */
+ MutableList(Iterable<?> contents, Mutability mutability) {
+ super();
+ addAll(contents);
+ this.mutability = mutability;
+ }
+
+ /**
+ * Creates a MutableList from contents and an Environment.
+ * @param contents the contents of the list
+ * @param env an Environment from which to inherit Mutability, or null for immutable
+ * @return a MutableList containing the elements
+ */
+ public MutableList(Iterable<?> contents, @Nullable Environment env) {
+ this(contents, env == null ? Mutability.IMMUTABLE : env.mutability());
+ }
+
+ /**
+ * Creates a MutableList from contents and an Environment.
+ * @param contents the contents of the list
+ * @return an actually immutable MutableList containing the elements
+ */
+ public MutableList(Iterable<?> contents) {
+ this(contents, Mutability.IMMUTABLE);
+ }
+
+ /**
+ * Adds one element at the end of the MutableList.
+ * @param element the element to add
+ */
+ private void add(Object element) {
+ this.contents.add(element);
+ }
+
+ /**
+ * Adds all the elements at the end of the MutableList.
+ * @param elements the elements to add
+ */
+ private void addAll(Iterable<?> elements) {
+ for (Object elem : elements) {
+ add(elem);
+ }
+ }
+
+ private void checkMutable(Location loc, Environment env) throws EvalException {
+ try {
+ Mutability.checkMutable(this, env);
+ } catch (MutabilityException ex) {
+ throw new EvalException(loc, ex);
+ }
+ }
+
+ /**
+ * Adds one element at the end of the MutableList.
+ * @param element the element to add
+ * @param loc the Location at which to report any error
+ * @param env the Environment requesting the modification
+ */
+ public void add(Object element, Location loc, Environment env) throws EvalException {
+ checkMutable(loc, env);
+ add(element);
+ }
+
+ /**
+ * Adds all the elements at the end of the MutableList.
+ * @param elements the elements to add
+ * @param loc the Location at which to report any error
+ * @param env the Environment requesting the modification
+ */
+ public void addAll(Iterable<?> elements, Location loc, Environment env) throws EvalException {
+ checkMutable(loc, env);
+ addAll(elements);
+ }
+
+
+ @Override
+ public List<Object> getList() {
+ return contents;
}
@Override
- public Iterator<Object> iterator() {
- return ImmutableList.of().iterator();
+ public ImmutableList<Object> getImmutableList() {
+ return ImmutableList.copyOf(contents);
}
@Override
- public int size() {
- return 0;
+ public Mutability mutability() {
+ return mutability;
}
@Override
- public boolean isEmpty() {
+ public boolean isTuple() {
+ return false;
+ }
+
+ @Override
+ public boolean isImmutable() {
+ return false;
+ }
+
+ /**
+ * An empty IMMUTABLE MutableList.
+ */
+ public static final MutableList EMPTY = new MutableList(Tuple.EMPTY);
+ }
+
+ /**
+ * An immutable tuple, e.g. in (1, 2, 3)
+ */
+ @SkylarkModule(name = "tuple",
+ doc = "A language built-in type to support tuples. Example of tuple literal:<br>"
+ + "<pre class=language-python>x = (1, 2, 3)</pre>"
+ + "Accessing elements is possible using indexing (starts from <code>0</code>):<br>"
+ + "<pre class=language-python>e = x[1] # e == 2</pre>"
+ + "Lists support the <code>+</code> operator to concatenate two tuples. Example:<br>"
+ + "<pre class=language-python>x = (1, 2) + (3, 4) # x == (1, 2, 3, 4)\n"
+ + "x = (\"a\", \"b\")\n"
+ + "x += (\"c\",) # x == (\"a\", \"b\", \"c\")</pre>"
+ + "Tuples are immutable, therefore <code>x[1] = \"a\"</code> is not supported.")
+ @Immutable
+ public static final class Tuple extends SkylarkList {
+
+ private final ImmutableList<Object> contents;
+
+ private Tuple(ImmutableList<Object> contents) {
+ super();
+ this.contents = contents;
+ }
+
+ /**
+ * THE empty Skylark tuple.
+ */
+ public static final Tuple EMPTY = new Tuple(ImmutableList.of());
+
+ /**
+ * Creates a Tuple from an ImmutableList.
+ */
+ public static Tuple create(ImmutableList<Object> contents) {
+ if (contents.isEmpty()) {
+ return EMPTY;
+ }
+ return new Tuple(contents);
+ }
+
+ /**
+ * Creates a Tuple from an Iterable.
+ */
+ public static Tuple copyOf(Iterable<?> contents) {
+ return create(ImmutableList.copyOf(contents));
+ }
+
+ /**
+ * Builds a Skylark tuple from a variable number of arguments.
+ * @param elements a variable number of arguments (or an Array of Object-s)
+ * @return a Skylark tuple containing the specified arguments as elements.
+ */
+ public static Tuple of(Object... elements) {
+ return Tuple.create(ImmutableList.copyOf(elements));
+ }
+
+ @Override
+ public List<Object> getList() {
+ return contents;
+ }
+
+ @Override
+ public ImmutableList<Object> getImmutableList() {
+ return contents;
+ }
+
+ @Override
+ public boolean isTuple() {
return true;
}
@Override
- public Object get(int i) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public List<Object> toList() {
- return isTuple() ? ImmutableList.of() : Lists.newArrayList();
- }
-
- @Override
- public String toString() {
- return isTuple() ? "()" : "[]";
- }
- }
-
- /**
- * An empty Skylark 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, SkylarkType contentType) {
- super(tuple, contentType);
- this.list = Preconditions.checkNotNull(list);
- }
-
- @Override
- public Iterator<Object> iterator() {
- return list.iterator();
- }
-
- @Override
- public int size() {
- return list.size();
- }
-
- @Override
- public boolean isEmpty() {
- return list.isEmpty();
- }
-
- @Override
- public Object get(int i) {
- return list.get(i);
- }
-
- @Override
- public List<Object> toList() {
- return isTuple() ? list : Lists.newArrayList(list);
- }
- }
-
- /**
- * A Skylark list to support lazy iteration (i.e. we only call iterator on the object this
- * list masks when it's absolutely necessary). This is useful if iteration is expensive
- * (e.g. NestedSet-s). Size(), get() and isEmpty() are expensive operations but
- * concatenation is quick.
- */
- private static final class LazySkylarkList extends SkylarkList {
- private final Iterable<Object> iterable;
- private ImmutableList<Object> list = null;
-
- private LazySkylarkList(Iterable<Object> iterable, boolean tuple, SkylarkType contentType) {
- super(tuple, contentType);
- this.iterable = Preconditions.checkNotNull(iterable);
- }
-
- @Override
- public Iterator<Object> iterator() {
- return iterable.iterator();
- }
-
- @Override
- public int size() {
- return Iterables.size(iterable);
- }
-
- @Override
- public boolean isEmpty() {
- return Iterables.isEmpty(iterable);
- }
-
- @Override
- public Object get(int i) {
- return getList().get(i);
- }
-
- @Override
- public List<Object> toList() {
- ImmutableList<Object> result = getList();
- return isTuple() ? result : Lists.newArrayList(result);
- }
-
- private ImmutableList<Object> getList() {
- if (list == null) {
- list = ImmutableList.copyOf(iterable);
+ public boolean isImmutable() {
+ for (Object item : this) {
+ if (!EvalUtils.isImmutable(item)) {
+ return false;
+ }
}
- return list;
+ return true;
}
}
-
- /**
- * A Skylark list to support quick concatenation of lists. Concatenation is O(1),
- * size(), isEmpty() is O(n), get() is O(h).
- */
- private static final class ConcatenatedSkylarkList extends SkylarkList {
- private final SkylarkList left;
- private final SkylarkList right;
-
- private ConcatenatedSkylarkList(
- SkylarkList left, SkylarkList right, boolean tuple, SkylarkType contentType) {
- super(tuple, contentType);
- this.left = Preconditions.checkNotNull(left);
- this.right = Preconditions.checkNotNull(right);
- }
-
- @Override
- public Iterator<Object> iterator() {
- return Iterables.concat(left, right).iterator();
- }
-
- @Override
- public int size() {
- // We shouldn't evaluate the size function until it's necessary, because it can be expensive
- // for lazy lists (e.g. lists containing a NestedSet).
- // TODO(bazel-team): make this class more clever to store the size and empty parameters
- // for every non-LazySkylarkList member.
- return left.size() + right.size();
- }
-
- @Override
- public boolean isEmpty() {
- return left.isEmpty() && right.isEmpty();
- }
-
- @Override
- public Object get(int i) {
- int leftSize = left.size();
- if (i < leftSize) {
- return left.get(i);
- } else {
- return right.get(i - leftSize);
- }
- }
-
- @Override
- public List<Object> toList() {
- List<Object> result = ImmutableList.<Object>builder().addAll(left).addAll(right).build();
- return isTuple() ? result : Lists.newArrayList(result);
- }
- }
-
- /**
- * @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, SkylarkType contentType) {
- if (elements.isEmpty()) {
- return EMPTY_LIST;
- }
- 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));
- }
-
- /**
- * @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.
- * Only use if you already know for sure all elements are of the specified type.
- */
- @SuppressWarnings("unchecked")
- public static SkylarkList lazyList(Iterable<?> elements, SkylarkType contentType) {
- return new LazySkylarkList((Iterable<Object>) 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 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.
- * Only use if you already know for sure all elements are of the specified type.
- */
- @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 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, getContentType(elements));
- }
-
- private static SkylarkType getContentType(Collection<?> elements)
- throws EvalException {
- SkylarkType type = SkylarkType.TOP;
- for (Object element : elements) {
- SkylarkType elementType = SkylarkType.typeOf(element);
- type = SkylarkType.intersection(type, elementType);
- }
- return type;
- }
-
- /**
- * Returns a Skylark list created from Skylark lists left and right. Throws an exception
- * if they are not of the same generic type.
- */
- public static SkylarkList concat(SkylarkList left, SkylarkList right, Location loc)
- throws EvalException {
- if (left.isTuple() != right.isTuple()) {
- throw new EvalException(loc, "cannot concatenate lists and tuples");
- }
- if (left == EMPTY_LIST) {
- return right;
- }
- if (right == EMPTY_LIST) {
- return left;
- }
- SkylarkType type = SkylarkType.intersection(left.contentType, right.contentType);
- return new ConcatenatedSkylarkList(left, right, left.isTuple(), type);
- }
-
- /**
- * 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);
- }
-
- @Override
- public boolean isImmutable() {
- if (!isTuple()) {
- return false;
- }
- for (Object item : this) {
- if (!EvalUtils.isImmutable(item)) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- public void write(Appendable buffer, char quotationMark) {
- Printer.printList(buffer, toList(), isTuple(), quotationMark);
- }
}
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 498c66e..21db288 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
@@ -24,6 +24,8 @@
import com.google.common.collect.Interners;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
+import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
import java.io.Serializable;
import java.lang.reflect.Method;
@@ -174,16 +176,26 @@
/** 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 SEQUENCE type, that contains lists and tuples */
+ // TODO(bazel-team): this was added for backward compatibility with the BUILD language,
+ // that doesn't make a difference between list and tuple, so that functions can be declared
+ // that keep not making the difference. Going forward, though, we should investigate whether
+ // we ever want to use this type, and if not, make sure no existing client code uses it.
+ public static final Simple SEQUENCE = Simple.of(SkylarkList.class);
- /** The STRING_LIST type, a SkylarkList of strings */
+ /** The LIST type, that contains all MutableList-s */
+ public static final Simple LIST = Simple.of(MutableList.class);
+
+ /** The TUPLE type, that contains all Tuple-s */
+ public static final Simple TUPLE = Simple.of(Tuple.class);
+
+ /** The STRING_LIST type, a MutableList of strings */
public static final SkylarkType STRING_LIST = Combination.of(LIST, STRING);
- /** The INT_LIST type, a SkylarkList of integers */
+ /** The INT_LIST type, a MutableList 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 */
+ /** The SET type, that contains all SkylarkNestedSet-s, and the generic combinator for them */
public static final Simple SET = Simple.of(SkylarkNestedSet.class);
@@ -314,15 +326,26 @@
// 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;
}
+ if (other instanceof Combination) {
+ SkylarkType generic = genericType.intersectWith(((Combination) other).getGenericType());
+ if (generic == BOTTOM) {
+ return BOTTOM;
+ }
+ SkylarkType arg = intersection(argType, ((Combination) other).getArgType());
+ if (arg == BOTTOM) {
+ return BOTTOM;
+ }
+ return Combination.of(generic, arg);
+ }
+ if (other instanceof Simple) {
+ SkylarkType generic = genericType.intersectWith(other);
+ if (generic == BOTTOM) {
+ return BOTTOM;
+ }
+ return SkylarkType.of(generic, getArgType());
+ }
+ return BOTTOM;
}
@Override public boolean equals(Object other) {
@@ -477,9 +500,7 @@
}
public static SkylarkType of(Class<?> type) {
- if (SkylarkList.class.isAssignableFrom(type)) {
- return LIST;
- } else if (SkylarkNestedSet.class.isAssignableFrom(type)) {
+ if (SkylarkNestedSet.class.isAssignableFrom(type)) {
return SET;
} else if (BaseFunction.class.isAssignableFrom(type)) {
return new SkylarkFunctionType("unknown", TOP);
@@ -550,8 +571,6 @@
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 {
@@ -560,9 +579,7 @@
}
public static SkylarkType getGenericArgType(Object value) {
- if (value instanceof SkylarkList) {
- return ((SkylarkList) value).getContentType();
- } else if (value instanceof SkylarkNestedSet) {
+ if (value instanceof SkylarkNestedSet) {
return ((SkylarkNestedSet) value).getContentType();
} else {
return TOP;
@@ -584,7 +601,7 @@
static void checkTypeAllowedInSkylark(Object object, Location loc) throws EvalException {
if (!isTypeAllowedInSkylark(object)) {
throw new EvalException(loc,
- "Type is not allowed in Skylark: "
+ "Type is not allowed in Skylark: "
+ object.getClass().getSimpleName());
}
}
@@ -713,21 +730,22 @@
/**
* Converts an object retrieved from a Java method to a Skylark-compatible type.
*/
- static Object convertToSkylark(Object object, Method method) {
+ static Object convertToSkylark(Object object, Method method, @Nullable Environment env) {
if (object instanceof NestedSet<?>) {
return new SkylarkNestedSet(getGenericTypeFromMethod(method), (NestedSet<?>) object);
- } else if (object instanceof List<?>) {
- return SkylarkList.list((List<?>) object, getGenericTypeFromMethod(method));
}
- return object;
+ return convertToSkylark(object, env);
}
/**
* Converts an object to a Skylark-compatible type if possible.
*/
- public static Object convertToSkylark(Object object, Location loc) throws EvalException {
+ public static Object convertToSkylark(Object object, @Nullable Environment env) {
if (object instanceof List<?>) {
- return SkylarkList.list((List<?>) object, loc);
+ List<?> list = (List<?>) object;
+ // TODO(bazel-team): shouldn't we convert an ImmutableList into a Tuple?
+ // problem: we treat them sometimes as a tuple, sometimes as a list.
+ return new MutableList(list, env);
}
return object;
}
@@ -737,7 +755,7 @@
*/
public static Object convertFromSkylark(Object value) {
if (value instanceof SkylarkList) {
- return ((SkylarkList) value).toList();
+ return ((SkylarkList) value).getList();
}
return value;
}