bazel syntax: simplify and optimize list, tuple, range
Sequence:
- is now an interface
- delete getContentsUnsafe: don't prescribe representation.
- delete unnecessary stub methods from StarlarkValue and AbstractList.
- delete abstract repeat method. It is defined only for list and tuple.
Tuple:
- now extends AbstractList
- manipulates an Object[] directly, eliminating ImmutableList intermediary
...yet getImmutableList avoids a copy using the toArray sharing trick.
- eliminate all unnecessary array copies
- isHashable and isImmutable iterate over Object[] directly, not List wrapper.
StarlarkList
- now extends AbstractList
- delete one overload of StarlarkList.wrapUnsafe
- manipulates an Object[] directly, eliminating ArrayList intermediary.
- eliminate all unnecessary array copies
RangeList:
- optimize equals() not to materialize the sequence
- merge RangeList and RangeListView
- delete unreachable repeat() method
Also:
- removed comment at Sequence.createImmutable.
We should stop using StarlarkThread to mean Mutability.
(Longer term, we should get rid of Mutability in favor of a per-object frozen bit.)
PiperOrigin-RevId: 280680660
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Dict.java b/src/main/java/com/google/devtools/build/lib/syntax/Dict.java
index 257fb77..b34f094 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Dict.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Dict.java
@@ -22,7 +22,6 @@
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -191,7 +190,7 @@
Object key = keySet().iterator().next();
Object value = get(key);
remove(key, loc);
- return Tuple.of(key, value);
+ return Tuple.pair(key, value);
}
@SkylarkCallable(
@@ -280,11 +279,12 @@
+ "</pre>\n",
useStarlarkThread = true)
public StarlarkList<?> items(StarlarkThread thread) throws EvalException {
- ArrayList<Object> list = Lists.newArrayListWithCapacity(size());
- for (Map.Entry<?, ?> entries : entrySet()) {
- list.add(Tuple.of(entries.getKey(), entries.getValue()));
+ Object[] array = new Object[size()];
+ int i = 0;
+ for (Map.Entry<?, ?> e : entrySet()) {
+ array[i++] = Tuple.pair(e.getKey(), e.getValue());
}
- return StarlarkList.wrapUnsafe(thread, list);
+ return StarlarkList.wrap(thread.mutability(), array);
}
@SkylarkCallable(
@@ -295,11 +295,12 @@
+ "</pre>\n",
useStarlarkThread = true)
public StarlarkList<?> keys(StarlarkThread thread) throws EvalException {
- ArrayList<Object> list = Lists.newArrayListWithCapacity(size());
- for (Map.Entry<?, ?> entries : entrySet()) {
- list.add(entries.getKey());
+ Object[] array = new Object[size()];
+ int i = 0;
+ for (Map.Entry<?, ?> e : entrySet()) {
+ array[i++] = e.getKey();
}
- return StarlarkList.wrapUnsafe(thread, list);
+ return StarlarkList.wrap(thread.mutability(), array);
}
private static final Dict<?, ?> EMPTY = withMutability(Mutability.IMMUTABLE);
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Eval.java b/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
index 68d60d2..dd14952 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Eval.java
@@ -547,13 +547,12 @@
case LIST_EXPR:
{
ListExpression list = (ListExpression) expr;
- ArrayList<Object> result = new ArrayList<>(list.getElements().size());
- for (Expression elem : list.getElements()) {
- result.add(eval(thread, elem));
+ int n = list.getElements().size();
+ Object[] array = new Object[n];
+ for (int i = 0; i < n; i++) {
+ array[i] = eval(thread, list.getElements().get(i));
}
- return list.isTuple()
- ? Tuple.copyOf(result) // TODO(adonovan): opt: avoid copy
- : StarlarkList.wrapUnsafe(thread, result);
+ return list.isTuple() ? Tuple.wrap(array) : StarlarkList.wrap(thread.mutability(), array);
}
case SLICE:
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 723d49b..363a3a9 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
@@ -853,11 +853,11 @@
if (otherFactor instanceof Integer) {
return Math.multiplyExact(number, (Integer) otherFactor);
} else if (otherFactor instanceof String) {
- // Similar to Python, a factor < 1 leads to an empty string.
return Strings.repeat((String) otherFactor, Math.max(0, number));
- } else if (otherFactor instanceof Sequence && !(otherFactor instanceof RangeList)) {
- // Similar to Python, a factor < 1 leads to an empty string.
- return ((Sequence<?>) otherFactor).repeat(number, thread.mutability());
+ } else if (otherFactor instanceof Tuple) {
+ return ((Tuple<?>) otherFactor).repeat(number, thread.mutability());
+ } else if (otherFactor instanceof StarlarkList) {
+ return ((StarlarkList<?>) otherFactor).repeat(number, thread.mutability());
}
}
throw unknownBinaryOperator(x, y, TokenKind.STAR, location);
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 166a698..93acac3 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
@@ -36,6 +36,8 @@
import com.google.devtools.build.lib.syntax.StarlarkSemantics.FlagIdentifier;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
@@ -199,10 +201,10 @@
final StarlarkThread thread)
throws EvalException, InterruptedException {
- ArrayList<?> list = new ArrayList<>(EvalUtils.toCollection(iterable, loc, thread));
+ Object[] array = EvalUtils.toCollection(iterable, loc, thread).toArray();
if (key == Starlark.NONE) {
try {
- Collections.sort(list, EvalUtils.SKYLARK_COMPARATOR);
+ Arrays.sort(array, EvalUtils.SKYLARK_COMPARATOR);
} catch (EvalUtils.ComparisonException e) {
throw new EvalException(loc, e);
}
@@ -232,7 +234,7 @@
KeyComparator comp = new KeyComparator();
try {
- Collections.sort(list, comp);
+ Arrays.sort(array, comp);
} catch (EvalUtils.ComparisonException e) {
throw new EvalException(loc, e);
}
@@ -249,9 +251,13 @@
}
if (reverse) {
- Collections.reverse(list);
+ for (int i = 0, j = array.length - 1; i < j; i++, j--) {
+ Object tmp = array[i];
+ array[i] = array[j];
+ array[j] = tmp;
+ }
}
- return StarlarkList.wrapUnsafe(thread, list);
+ return StarlarkList.wrap(thread.mutability(), array);
}
@SkylarkCallable(
@@ -278,7 +284,7 @@
for (Object element : EvalUtils.toIterable(sequence, loc, thread)) {
tmpList.addFirst(element);
}
- return StarlarkList.copyOf(thread, tmpList);
+ return StarlarkList.copyOf(thread.mutability(), tmpList);
}
@SkylarkCallable(
@@ -320,7 +326,7 @@
useLocation = true,
useStarlarkThread = true)
public StarlarkList<?> list(Object x, Location loc, StarlarkThread thread) throws EvalException {
- return StarlarkList.copyOf(thread, EvalUtils.toCollection(x, loc, thread));
+ return StarlarkList.copyOf(thread.mutability(), EvalUtils.toCollection(x, loc, thread));
}
@SkylarkCallable(
@@ -633,13 +639,14 @@
useLocation = true)
public StarlarkList<?> enumerate(Object input, Integer start, Location loc, StarlarkThread thread)
throws EvalException {
- int count = start;
- ArrayList<Sequence<?>> result = new ArrayList<>();
- for (Object obj : EvalUtils.toCollection(input, loc, thread)) {
- result.add(Tuple.of(count, obj));
- count++;
+ Collection<?> src = EvalUtils.toCollection(input, loc, thread);
+ Object[] array = new Object[src.size()];
+ int i = 0;
+ for (Object x : src) {
+ array[i] = Tuple.pair(i + start, x);
+ i++;
}
- return StarlarkList.wrapUnsafe(thread, result);
+ return StarlarkList.wrap(thread.mutability(), array);
}
@SkylarkCallable(
@@ -719,7 +726,7 @@
if (step == 0) {
throw new EvalException(loc, "step cannot be 0");
}
- return RangeList.of(start, stop, step);
+ return new RangeList(start, stop, step);
}
/** Returns true if the object has a field of the given name, otherwise false. */
@@ -820,7 +827,7 @@
fields.addAll(((ClassObject) object).getFieldNames());
}
fields.addAll(CallUtils.getMethodNames(thread.getSemantics(), object.getClass()));
- return StarlarkList.copyOf(thread, fields);
+ return StarlarkList.copyOf(thread.mutability(), fields);
}
@SkylarkCallable(
@@ -1204,7 +1211,7 @@
result.add(Tuple.copyOf(elem));
}
} while (allHasNext);
- return StarlarkList.wrapUnsafe(thread, result);
+ return StarlarkList.copyOf(thread.mutability(), result);
}
/** Skylark int type. */
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/RangeList.java b/src/main/java/com/google/devtools/build/lib/syntax/RangeList.java
index 275b0b9..4a40253 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/RangeList.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/RangeList.java
@@ -15,7 +15,6 @@
package com.google.devtools.build.lib.syntax;
import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.UnmodifiableIterator;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.events.Location;
@@ -24,7 +23,6 @@
import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
import java.util.AbstractList;
import java.util.Iterator;
-import java.util.List;
import java.util.NoSuchElementException;
/**
@@ -53,34 +51,87 @@
+ "range(10)[3:0:-1] # range(3, 0, -1)</pre>"
+ "Ranges are immutable, as in Python 3.")
@Immutable
-final class RangeList extends Sequence<Integer> {
+final class RangeList extends AbstractList<Integer> implements Sequence<Integer> {
- private final int step;
private final int start;
+ private final int stop;
+ private final int step;
+ private final int size; // (derived)
- private static int computeItem(int start, int step, int index) {
+ RangeList(int start, int stop, int step) {
+ Preconditions.checkArgument(step != 0);
+
+ this.start = start;
+ this.stop = stop;
+ this.step = step;
+
+ // compute size.
+ // Python version:
+ // https://github.com/python/cpython/blob/09bb918a61031377d720f1a0fa1fe53c962791b6/Objects/rangeobject.c#L144
+ int low; // [low,high) is a half-open interval
+ int high;
+ if (step > 0) {
+ low = start;
+ high = stop;
+ } else {
+ low = stop;
+ high = start;
+ step = -step;
+ }
+ if (low >= high) {
+ this.size = 0;
+ } else {
+ int diff = high - low - 1;
+ this.size = diff / step + 1;
+ }
+ }
+
+ @Override
+ public Integer get(int index) {
+ if (index < 0 || index >= size()) {
+ throw new ArrayIndexOutOfBoundsException(index + ":" + this);
+ }
return start + step * index;
}
- /** Provides access to range elements based on their index. */
- private static class RangeListView extends AbstractList<Integer> {
+ @Override
+ public int size() {
+ return size;
+ }
- /** Iterator for increasing/decreasing sequences. */
- private static class RangeListIterator extends UnmodifiableIterator<Integer> {
- private final int stop;
- private final int step;
+ @Override
+ public int hashCode() {
+ return 7873 ^ (5557 * start) ^ (3251 * step) ^ (1091 * size);
+ }
- private int cursor;
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof RangeList)) {
+ return false;
+ }
+ RangeList that = (RangeList) other;
- private RangeListIterator(int start, int stop, int step) {
- this.cursor = start;
- this.stop = stop;
- this.step = step;
- }
+ // Two RangeLists compare equal if they denote the same sequence.
+ if (this.size != that.size) {
+ return false; // sequences differ in length
+ }
+ if (this.size == 0) {
+ return true; // both sequences are empty
+ }
+ if (this.start != that.start) {
+ return false; // first element differs
+ }
+ return this.size == 1 || this.step == that.step;
+ }
+
+ @Override
+ public Iterator<Integer> iterator() {
+ return new UnmodifiableIterator<Integer>() {
+ int cursor = start;
@Override
public boolean hasNext() {
- return (step > 0) ? cursor < stop : cursor > stop;
+ return (step > 0) ? (cursor < stop) : (cursor > stop);
}
@Override
@@ -92,94 +143,7 @@
cursor += step;
return current;
}
- }
-
- /**
- * @return The size of the range specified by {@code start}, {@code stop} and {@code step}.
- * Python version:
- * https://github.com/python/cpython/blob/09bb918a61031377d720f1a0fa1fe53c962791b6/Objects/rangeobject.c#L144
- */
- private static int computeSize(int start, int stop, int step) {
- // low and high represent bounds of the interval with only one of the sides being open.
- int low;
- int high;
- if (step > 0) {
- low = start;
- high = stop;
- } else {
- low = stop;
- high = start;
- step = -step;
- }
- if (low >= high) {
- return 0;
- }
-
- int diff = high - low - 1;
- return diff / step + 1;
- }
-
- private final int start;
- private final int stop;
- private final int step;
- private final int size;
-
- private RangeListView(int start, int stop, int step) {
- this.start = start;
- this.stop = stop;
- this.step = step;
- this.size = computeSize(start, stop, step);
- }
-
- @Override
- public Integer get(int index) {
- if (index < 0 || index >= size()) {
- throw new ArrayIndexOutOfBoundsException(index);
- }
- return computeItem(start, step, index);
- }
-
- @Override
- public int size() {
- return size;
- }
-
- /**
- * Returns an iterator optimized for traversing range elements, since it's the most frequent
- * operation for which ranges are used.
- */
- @Override
- public Iterator<Integer> iterator() {
- return new RangeListIterator(start, stop, step);
- }
-
- /** @return the start of the range. */
- public int getStart() {
- return start;
- }
-
- /** @return the stop element (next after the last one) of the range. */
- public int getStop() {
- return stop;
- }
-
- /** @return the step between each element of the range. */
- public int getStep() {
- return step;
- }
- }
-
- private final RangeListView contents;
-
- private RangeList(int start, int stop, int step) {
- this.step = step;
- this.start = start;
- this.contents = new RangeListView(start, stop, step);
- }
-
- @Override
- public ImmutableList<Integer> getImmutableList() {
- return ImmutableList.copyOf(contents);
+ };
}
@Override
@@ -188,41 +152,26 @@
throws EvalException {
Slice slice = Slice.from(size(), start, end, step, loc);
int substep = slice.step * this.step;
- int substart = computeItem(this.start, this.step, slice.start);
- int substop = computeItem(this.start, this.step, slice.stop);
- return RangeList.of(substart, substop, substep);
+ // TODO(adonovan): why no bounds check here?
+ int substart = getNoCheck(slice.start);
+ int substop = getNoCheck(slice.stop);
+ return new RangeList(substart, substop, substep);
}
- @Override
- public Sequence<Integer> repeat(int times, Mutability mutability) {
- throw new UnsupportedOperationException("Ranges do not support repetition.");
- }
-
- @Override
- protected List<Integer> getContentsUnsafe() {
- return contents;
+ private int getNoCheck(int i) {
+ return start + step * i;
}
@Override
public void repr(SkylarkPrinter printer) {
- if (contents.getStep() == 1) {
- printer.format("range(%d, %d)", contents.getStart(), contents.getStop());
+ if (step == 1) {
+ printer.format("range(%d, %d)", start, stop);
} else {
- printer.format(
- "range(%d, %d, %d)", contents.getStart(), contents.getStop(), contents.getStep());
+ printer.format("range(%d, %d, %d)", start, stop, step);
}
}
/**
- * @return A half-opened range defined by its starting value (inclusive), stop value (exclusive)
- * and a step from previous value to the next one.
- */
- static RangeList of(int start, int stop, int step) {
- Preconditions.checkArgument(step != 0);
- return new RangeList(start, stop, step);
- }
-
- /**
* Represents a slice produced by applying {@code [start:end:step]} to a {@code range}.
*
* <p>{@code start} and {@code stop} define a half-open interval
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Sequence.java b/src/main/java/com/google/devtools/build/lib/syntax/Sequence.java
index 329c41a..ff9b4da 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Sequence.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Sequence.java
@@ -15,17 +15,12 @@
package com.google.devtools.build.lib.syntax;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterators;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
-import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
-import java.util.Collection;
import java.util.Collections;
-import java.util.Iterator;
import java.util.List;
-import java.util.ListIterator;
import java.util.RandomAccess;
import javax.annotation.Nullable;
@@ -40,15 +35,17 @@
documented = false,
category = SkylarkModuleCategory.BUILTIN,
doc = "common type of lists and tuples.")
-public abstract class Sequence<E> implements SkylarkValue, List<E>, RandomAccess, SkylarkIndexable {
+public interface Sequence<E> extends SkylarkValue, List<E>, RandomAccess, SkylarkIndexable {
@Override
- public final boolean truth() {
+ default boolean truth() {
return !isEmpty();
}
/** Returns an ImmutableList object with the current underlying contents of this Sequence. */
- public abstract ImmutableList<E> getImmutableList();
+ default ImmutableList<E> getImmutableList() {
+ return ImmutableList.copyOf(this);
+ }
/**
* Retrieve an entry from a Sequence.
@@ -58,20 +55,13 @@
* @throws EvalException if the key is invalid
*/
@Override
- public E getIndex(Object key, Location loc) throws EvalException {
- List<E> list = getContentsUnsafe();
- int index = EvalUtils.getSequenceIndex(key, list.size(), loc);
- return list.get(index);
+ default E getIndex(Object key, Location loc) throws EvalException {
+ return get(EvalUtils.getSequenceIndex(key, size(), loc));
}
@Override
- public boolean containsKey(Object key, Location loc) throws EvalException {
- for (Object obj : this) {
- if (obj.equals(key)) {
- return true;
- }
- }
- return false;
+ default boolean containsKey(Object key, Location loc) throws EvalException {
+ return contains(key);
}
/**
@@ -83,48 +73,10 @@
* @see EvalUtils#getSliceIndices
* @throws EvalException if the key is invalid; uses {@code loc} for error reporting
*/
- public abstract Sequence<E> getSlice(
- Object start, Object end, Object step, Location loc, Mutability mutability)
+ Sequence<E> getSlice(Object start, Object end, Object step, Location loc, Mutability mutability)
throws EvalException;
/**
- * Constructs a repetition of this {@code Sequence}.
- *
- * <p>{@code mutability} will be used for the resulting list. If it is null, the list will be
- * immutable. For {@code Tuple}s, which are always immutable, this argument is ignored.
- */
- // TODO(adonovan): remove this method and handle only int*{list,tuple} in EvalUtils.mult.
- // In particular, reject int*range. (In principal this is a breaking change.)
- public abstract Sequence<E> repeat(int times, Mutability mutability);
-
- @Override
- public void repr(SkylarkPrinter printer) {
- printer.printList(getContentsUnsafe(), this instanceof Tuple);
- }
-
- @Override
- public String toString() {
- return Printer.repr(this);
- }
-
- // Note that the following two functions slightly violate the Java List protocol,
- // in that it does NOT consider that a Sequence .equals() an arbitrary List with same contents.
- // This is because we use .equals() to model skylark equality, which like Python
- // distinguishes a StarlarkList from a Tuple.
- @Override
- public boolean equals(Object object) {
- return (this == object)
- || ((object != null)
- && (this.getClass() == object.getClass())
- && getContentsUnsafe().equals(((Sequence) object).getContentsUnsafe()));
- }
-
- @Override
- public int hashCode() {
- return getClass().hashCode() + 31 * getContentsUnsafe().hashCode();
- }
-
- /**
* Casts a {@code List<?>} to an unmodifiable {@code List<T>}, after checking that its contents
* all have type {@code type}.
*
@@ -137,6 +89,7 @@
// We could have used bounded generics to ensure that only downcasts are possible (i.e. cast
// List<S> to List<T extends S>), but this would be less convenient for some callers, and it would
// disallow casting an empty list to any type.
+ // TODO(adonovan): this method doesn't belong here.
@SuppressWarnings("unchecked")
public static <T> List<T> castList(List<?> list, Class<T> type, @Nullable String description)
throws EvalException {
@@ -158,6 +111,7 @@
* @param type the expected type of all the list's elements
* @param description a description of the argument being converted, or null, for debugging
*/
+ // TODO(adonovan): this method doesn't belong here.
public static <T> List<T> castSkylarkListOrNoneToList(
Object obj, Class<T> type, @Nullable String description) throws EvalException {
if (EvalUtils.isNullOrNone(obj)) {
@@ -172,168 +126,25 @@
}
/**
- * Casts this list as an unmodifiable {@code List<T>}, after checking that each element has
- * type {@code type}.
+ * Casts this list as an unmodifiable {@code List<T>}, after checking that each element has type
+ * {@code type}.
*
* @param type the expected type of all the list's elements
* @param description a description of the argument being converted, or null, for debugging
*/
- public <T> List<T> getContents(Class<T> type, @Nullable String description)
+ // TODO(adonovan): this method doesn't belong here.
+ default <T> List<T> getContents(Class<T> type, @Nullable String description)
throws EvalException {
- return castList(getContentsUnsafe(), type, description);
+ return castList(this, type, description);
}
/**
- * Creates an immutable Skylark list with the given elements.
- *
- * <p>It is unspecified whether this is a Skylark list or tuple. For more control, use one of the
- * factory methods in {@link StarlarkList} or {@link Tuple}.
+ * Creates an immutable StarlarkList with the given elements.
*
* <p>The caller must ensure that the elements of {@code contents} are not mutable.
*/
- // TODO(bazel-team): Eliminate this function in favor of a new StarlarkList factory method. With
- // such a method, we may no longer need to take null as a possible value for the Mutability or
- // StarlarkThread. That in turn would allow us to overload StarlarkList#of to take either a
- // Mutability or StarlarkThread.
+ // TODO(adonovan): move to StarlarkList.
public static <E> Sequence<E> createImmutable(Iterable<? extends E> contents) {
return StarlarkList.copyOf(Mutability.IMMUTABLE, contents);
}
-
- // methods of java.util.List
-
- // read operations
- //
- // TODO(adonovan): opt: push all read operations down into the subclasses
- // (list and tuple), as the getContentsUnsafe abstraction forces the internals
- // to be expressed in terms of Lists, which requires either a wasteful indirect
- // representation, or a wasteful lazy allocation of a list wrapper.
- protected abstract List<E> getContentsUnsafe();
-
- @Override
- public boolean contains(@Nullable Object object) {
- return getContentsUnsafe().contains(object);
- }
-
- @Override
- public boolean containsAll(Collection<?> collection) {
- return getContentsUnsafe().containsAll(collection);
- }
-
- @Override
- public E get(int i) {
- return getContentsUnsafe().get(i);
- }
-
- @Override
- public int indexOf(Object element) {
- return getContentsUnsafe().indexOf(element);
- }
-
- @Override
- public boolean isEmpty() {
- return getContentsUnsafe().isEmpty();
- }
-
- @Override
- public Iterator<E> iterator() {
- return Iterators.unmodifiableIterator(getContentsUnsafe().iterator());
- }
-
- @Override
- public int lastIndexOf(Object element) {
- return getContentsUnsafe().lastIndexOf(element);
- }
-
- @Override
- public ListIterator<E> listIterator() {
- return Collections.unmodifiableList(getContentsUnsafe()).listIterator();
- }
-
- @Override
- public ListIterator<E> listIterator(int index) {
- return Collections.unmodifiableList(getContentsUnsafe()).listIterator(index);
- }
-
- @Override
- public List<E> subList(int fromIndex, int toIndex) {
- return Collections.unmodifiableList(getContentsUnsafe()).subList(fromIndex, toIndex);
- }
-
- @Override
- public int size() {
- return getContentsUnsafe().size();
- }
-
- // toArray() and toArray(T[]) return copies, so we don't need an unmodifiable view.
- @Override
- public Object[] toArray() {
- return getContentsUnsafe().toArray();
- }
-
- @Override
- public <T> T[] toArray(T[] other) {
- return getContentsUnsafe().toArray(other);
- }
-
- // modify operations
-
- @Deprecated
- @Override
- public final boolean add(E element) {
- throw new UnsupportedOperationException();
- }
-
- @Deprecated
- @Override
- public final void add(int index, E element) {
- throw new UnsupportedOperationException();
- }
-
- @Deprecated
- @Override
- public final boolean addAll(int index, Collection<? extends E> elements) {
- throw new UnsupportedOperationException();
- }
-
- @Deprecated
- @Override
- public final boolean addAll(Collection<? extends E> collection) {
- throw new UnsupportedOperationException();
- }
-
- @Deprecated
- @Override
- public final void clear() {
- throw new UnsupportedOperationException();
- }
-
- @Deprecated
- @Override
- public final E remove(int index) {
- throw new UnsupportedOperationException();
- }
-
- @Deprecated
- @Override
- public final boolean remove(Object object) {
- throw new UnsupportedOperationException();
- }
-
- @Deprecated
- @Override
- public final boolean removeAll(Collection<?> collection) {
- throw new UnsupportedOperationException();
- }
-
- @Deprecated
- @Override
- public final boolean retainAll(Collection<?> collection) {
- throw new UnsupportedOperationException();
- }
-
- @Deprecated
- @Override
- public final E set(int index, E element) {
- throw new UnsupportedOperationException();
- }
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkList.java b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkList.java
index 7a21223..dbcf763 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/StarlarkList.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/StarlarkList.java
@@ -14,16 +14,16 @@
package com.google.devtools.build.lib.syntax;
-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.events.Location;
import com.google.devtools.build.lib.skylarkinterface.Param;
import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
-import java.util.ArrayList;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
+import java.util.AbstractList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nullable;
@@ -46,34 +46,29 @@
+ "['a', 'b', 'c', 'd'][::2] # ['a', 'c']\n"
+ "['a', 'b', 'c', 'd'][3:0:-1] # ['d', 'c', 'b']</pre>"
+ "Lists are mutable, as in Python.")
-public final class StarlarkList<E> extends Sequence<E> implements StarlarkMutable {
+public final class StarlarkList<E> extends AbstractList<E> implements Sequence<E>, StarlarkMutable {
- private final ArrayList<E> contents;
+ private int size;
+ private Object[] elems = EMPTY_ARRAY;
/** Final except for {@link #unsafeShallowFreeze}; must not be modified any other way. */
private Mutability mutability;
- private StarlarkList(ArrayList<E> rawContents, @Nullable Mutability mutability) {
- this.contents = Preconditions.checkNotNull(rawContents);
+ private static final Object[] EMPTY_ARRAY = {};
+
+ private StarlarkList(@Nullable Mutability mutability, Object[] elems) {
+ this.elems = elems;
+ this.size = elems.length;
this.mutability = mutability == null ? Mutability.IMMUTABLE : mutability;
}
/**
- * Creates an instance, taking ownership of the supplied {@link ArrayList}. This is exposed for
- * performance reasons. May be used when the calling code will not modify the supplied list after
- * calling (honor system).
+ * Takes ownership of the supplied array and returns a new StarlarkList instance that initially
+ * wraps the array. The caller must not subsequently modify the array, but the StarlarkList
+ * instance may do so.
*/
- static <T> StarlarkList<T> wrapUnsafe(@Nullable StarlarkThread thread, ArrayList<T> rawContents) {
- return wrapUnsafe(thread == null ? null : thread.mutability(), rawContents);
- }
-
- /**
- * Create an instance, taking ownership of the supplied {@link ArrayList}. This is exposed for
- * performance reasons. May be used when the calling code will not modify the supplied list after
- * calling (honor system).
- */
- static <T> StarlarkList<T> wrapUnsafe(@Nullable Mutability mutability, ArrayList<T> rawContents) {
- return new StarlarkList<>(rawContents, mutability);
+ static <T> StarlarkList<T> wrap(@Nullable Mutability mutability, Object[] elems) {
+ return new StarlarkList<>(mutability, elems);
}
@Override
@@ -93,10 +88,9 @@
* environments were then frozen. This instance is for empty lists that were always frozen from
* the beginning.
*/
- private static final StarlarkList<?> EMPTY =
- StarlarkList.copyOf(Mutability.IMMUTABLE, ImmutableList.of());
+ private static final StarlarkList<?> EMPTY = wrap(Mutability.IMMUTABLE, EMPTY_ARRAY);
- /** Returns an empty frozen list, cast to have an arbitrary content type. */
+ /** Returns an empty frozen list of the desired type. */
@SuppressWarnings("unchecked")
public static <T> StarlarkList<T> empty() {
return (StarlarkList<T>) EMPTY;
@@ -107,28 +101,34 @@
* {@link Mutability}. If {@code mutability} is null, the list is immutable.
*/
public static <T> StarlarkList<T> copyOf(
- @Nullable Mutability mutability, Iterable<? extends T> contents) {
- return new StarlarkList<>(Lists.newArrayList(contents), mutability);
+ @Nullable Mutability mutability, Iterable<? extends T> elems) {
+ return wrap(mutability, Iterables.toArray(elems, Object.class));
}
/**
* Returns a {@code StarlarkList} whose items are given by an iterable and which has the {@link
* Mutability} belonging to the given {@link StarlarkThread}. If {@code thread} is null, the list
* is immutable.
+ *
+ * @deprecated call {@code copyOf(thread.mutability(), elems)} instead.
*/
+ @Deprecated
public static <T> StarlarkList<T> copyOf(
- @Nullable StarlarkThread thread, Iterable<? extends T> contents) {
- return StarlarkList.copyOf(thread == null ? null : thread.mutability(), contents);
+ @Nullable StarlarkThread thread, Iterable<? extends T> elems) {
+ Mutability mu = thread == null ? null : thread.mutability();
+ return copyOf(mu, elems);
}
/**
* Returns a {@code StarlarkList} with the given items and the {@link Mutability} of the given
* {@link StarlarkThread}. If {@code thread} is null, the list is immutable.
+ *
+ * @deprecated call {@code of(thread.mutability(), elems)} instead.
*/
- public static <T> StarlarkList<T> of(@Nullable StarlarkThread thread, T... contents) {
- // Safe since we're taking a copy of the input.
- return StarlarkList.wrapUnsafe(
- thread == null ? null : thread.mutability(), Lists.newArrayList(contents));
+ @Deprecated
+ public static <T> StarlarkList<T> of(@Nullable StarlarkThread thread, T... elems) {
+ Mutability mu = thread == null ? null : thread.mutability();
+ return wrap(mu, Arrays.copyOf(elems, elems.length));
}
@Override
@@ -144,12 +144,13 @@
@Override
public ImmutableList<E> getImmutableList() {
- return ImmutableList.copyOf(contents);
- }
+ // Optimization: a frozen array needn't be copied.
+ // If the entire array is full, we can wrap it directly.
+ if (elems.length == size && mutability().isFrozen()) {
+ return Tuple.wrapImmutable(elems);
+ }
- @Override
- protected List<E> getContentsUnsafe() {
- return contents;
+ return ImmutableList.copyOf(this);
}
/**
@@ -157,46 +158,84 @@
* new list will have the given {@link Mutability}.
*/
public static <T> StarlarkList<T> concat(
- StarlarkList<? extends T> left, StarlarkList<? extends T> right, Mutability mutability) {
-
- ArrayList<T> newContents = new ArrayList<>(left.size() + right.size());
- addAll(newContents, left.contents);
- addAll(newContents, right.contents);
- return new StarlarkList<>(newContents, mutability);
- }
-
- /** More efficient {@link List#addAll} replacement when both lists are {@link ArrayList}s. */
- private static <T> void addAll(ArrayList<T> addTo, ArrayList<? extends T> addFrom) {
- // Hot code path, skip iterator.
- for (int i = 0; i < addFrom.size(); i++) {
- addTo.add(addFrom.get(i));
- }
+ StarlarkList<? extends T> x, StarlarkList<? extends T> y, Mutability mutability) {
+ Object[] res = new Object[x.size + y.size];
+ System.arraycopy(x.elems, 0, res, 0, x.size);
+ System.arraycopy(y.elems, 0, res, x.size, y.size);
+ return wrap(mutability, res);
}
@Override
- public StarlarkList<E> repeat(int times, Mutability mutability) {
- if (times <= 0) {
- return StarlarkList.wrapUnsafe(mutability, new ArrayList<>());
+ public boolean equals(Object that) {
+ // This slightly violates the java.util.List equivalence contract
+ // because it considers the class, not just the elements.
+ return this == that || (that instanceof StarlarkList && sameElems(this, ((StarlarkList) that)));
+ }
+
+ private static boolean sameElems(StarlarkList<?> x, StarlarkList<?> y) {
+ if (x.size != y.size) {
+ return false;
+ }
+ for (int i = 0; i < x.size; i++) {
+ if (!x.elems[i].equals(y.elems[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return 6047 + 4673 * Arrays.hashCode(elems);
+ }
+
+ @Override
+ public void repr(SkylarkPrinter printer) {
+ printer.printList(this, /*isTuple=*/ false);
+ }
+
+ // TODO(adonovan): SkylarkValue has 3 String methods yet still we need this fourth. Why?
+ @Override
+ public String toString() {
+ return Printer.repr(this);
+ }
+
+ /** Returns a new StarlarkList containing n consecutive repeats of this tuple. */
+ public StarlarkList<E> repeat(int n, Mutability mutability) {
+ if (n <= 0) {
+ return wrap(mutability, EMPTY_ARRAY);
}
- ArrayList<E> repeated = new ArrayList<>(this.size() * times);
- for (int i = 0; i < times; i++) {
- repeated.addAll(this);
+ // TODO(adonovan): reject unreasonably large n.
+ Object[] res = new Object[n * size];
+ for (int i = 0; i < n; i++) {
+ System.arraycopy(elems, 0, res, i * size, size);
}
- return StarlarkList.wrapUnsafe(mutability, repeated);
+ return wrap(mutability, res);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public E get(int i) {
+ return (E) elems[i]; // unchecked
+ }
+
+ @Override
+ public int size() {
+ return size;
}
@Override
public StarlarkList<E> getSlice(
Object start, Object end, Object step, Location loc, Mutability mutability)
throws EvalException {
- List<Integer> sliceIndices = EvalUtils.getSliceIndices(start, end, step, this.size(), loc);
- ArrayList<E> list = new ArrayList<>(sliceIndices.size());
- // foreach is not used to avoid iterator overhead
- for (int i = 0; i < sliceIndices.size(); ++i) {
- list.add(this.get(sliceIndices.get(i)));
+ // TODO(adonovan): this is horribly inefficient.
+ List<Integer> indices = EvalUtils.getSliceIndices(start, end, step, size(), loc);
+ Object[] array = new Object[indices.size()];
+ for (int i = 0; i < indices.size(); ++i) {
+ array[i] = elems[indices.get(i)];
}
- return StarlarkList.wrapUnsafe(mutability, list);
+ return wrap(mutability, array);
}
/**
@@ -207,7 +246,20 @@
*/
public void add(E element, Location loc) throws EvalException {
checkMutable(loc);
- contents.add(element);
+ grow(size + 1);
+ elems[size++] = element;
+ }
+
+ // Postcondition: elems.length >= mincap.
+ private void grow(int mincap) {
+ int oldcap = elems.length;
+ if (oldcap < mincap) {
+ int newcap = oldcap + (oldcap >> 1); // grow by at least 50%
+ if (newcap < mincap) {
+ newcap = mincap;
+ }
+ elems = Arrays.copyOf(elems, newcap);
+ }
}
/**
@@ -219,7 +271,10 @@
*/
public void add(int index, E element, Location loc) throws EvalException {
checkMutable(loc);
- contents.add(index, element);
+ grow(size + 1);
+ System.arraycopy(elems, index, elems, index + 1, size - index);
+ elems[index] = element;
+ size++;
}
/**
@@ -230,7 +285,34 @@
*/
public void addAll(Iterable<? extends E> elements, Location loc) throws EvalException {
checkMutable(loc);
- Iterables.addAll(contents, elements);
+ if (elements instanceof StarlarkList) {
+ if (this == elements) {
+ // special case: x.addAll(x)
+ grow(size + size);
+ System.arraycopy(elems, 0, elems, size, size);
+ size += size;
+ } else {
+ // another list
+ StarlarkList<?> src = (StarlarkList) elements;
+ grow(this.size + src.size);
+ System.arraycopy(src.elems, 0, this.elems, this.size, src.size);
+ this.size += src.size;
+ }
+ } else if (elements instanceof Collection) {
+ // collection of known size
+ Collection<?> src = (Collection) elements;
+ int srcsize = src.size();
+ grow(size + srcsize);
+ for (Object x : src) {
+ elems[size++] = x;
+ }
+ } else {
+ // iterable
+ for (Object x : elements) {
+ grow(size + 1);
+ elems[size++] = x;
+ }
+ }
}
/**
@@ -242,7 +324,11 @@
*/
public void remove(int index, Location loc) throws EvalException {
checkMutable(loc);
- contents.remove(index);
+ int n = size - index - 1;
+ if (n > 0) {
+ System.arraycopy(elems, index + 1, elems, index, n);
+ }
+ elems[--size] = null; // aid GC
}
@SkylarkCallable(
@@ -253,8 +339,8 @@
parameters = {@Param(name = "x", type = Object.class, doc = "The object to remove.")},
useLocation = true)
public NoneType removeObject(Object x, Location loc) throws EvalException {
- for (int i = 0; i < size(); i++) {
- if (get(i).equals(x)) {
+ for (int i = 0; i < size; i++) {
+ if (elems[i].equals(x)) {
remove(i, loc);
return Starlark.NONE;
}
@@ -272,7 +358,7 @@
*/
public void set(int index, E value, Location loc) throws EvalException {
checkMutable(loc);
- contents.set(index, value);
+ elems[index] = value;
}
@SkylarkCallable(
@@ -282,9 +368,9 @@
@Param(name = "item", type = Object.class, doc = "Item to add at the end.", noneable = true)
},
useLocation = true)
- @SuppressWarnings("unchecked") // Cast of Object item to E
+ @SuppressWarnings("unchecked")
public NoneType append(Object item, Location loc) throws EvalException {
- add((E) item, loc);
+ add((E) item, loc); // unchecked
return Starlark.NONE;
}
@@ -294,7 +380,8 @@
useLocation = true)
public NoneType clearMethod(Location loc) throws EvalException {
checkMutable(loc);
- contents.clear();
+ Arrays.fill(elems, null); // aid GC
+ size = 0;
return Starlark.NONE;
}
@@ -306,9 +393,9 @@
@Param(name = "item", type = Object.class, doc = "The item.", noneable = true)
},
useLocation = true)
- @SuppressWarnings("unchecked") // Cast of Object item to E
+ @SuppressWarnings("unchecked")
public NoneType insert(Integer index, Object item, Location loc) throws EvalException {
- add(EvalUtils.clampRangeEndpoint(index, size()), (E) item, loc);
+ add(EvalUtils.clampRangeEndpoint(index, size), (E) item, loc); // unchecked
return Starlark.NONE;
}
@@ -318,9 +405,11 @@
parameters = {@Param(name = "items", type = Object.class, doc = "Items to add at the end.")},
useLocation = true,
useStarlarkThread = true)
- @SuppressWarnings("unchecked")
public NoneType extend(Object items, Location loc, StarlarkThread thread) throws EvalException {
- addAll((Collection<? extends E>) EvalUtils.toCollection(items, loc, thread), loc);
+ @SuppressWarnings("unchecked")
+ Collection<? extends E> src =
+ (Collection<? extends E>) EvalUtils.toCollection(items, loc, thread);
+ addAll(src, loc);
return Starlark.NONE;
}
@@ -348,17 +437,12 @@
},
useLocation = true)
public Integer index(Object x, Object start, Object end, Location loc) throws EvalException {
- int i = start == Starlark.NONE ? 0 : EvalUtils.clampRangeEndpoint((Integer) start, this.size());
- int j =
- end == Starlark.NONE
- ? this.size()
- : EvalUtils.clampRangeEndpoint((Integer) end, this.size());
-
- while (i < j) {
- if (this.get(i).equals(x)) {
+ int i = start == Starlark.NONE ? 0 : EvalUtils.clampRangeEndpoint((Integer) start, size);
+ int j = end == Starlark.NONE ? size : EvalUtils.clampRangeEndpoint((Integer) end, size);
+ for (; i < j; i++) {
+ if (elems[i].equals(x)) {
return i;
}
- i++;
}
throw new EvalException(loc, Printer.format("item %r not found in list", x));
}
@@ -380,8 +464,8 @@
useLocation = true)
public Object pop(Object i, Location loc) throws EvalException {
int arg = i == Starlark.NONE ? -1 : (Integer) i;
- int index = EvalUtils.getSequenceIndex(arg, size(), loc);
- Object result = get(index);
+ int index = EvalUtils.getSequenceIndex(arg, size, loc);
+ Object result = elems[index];
remove(index, loc);
return result;
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/StringModule.java b/src/main/java/com/google/devtools/build/lib/syntax/StringModule.java
index a468cae..fc190dd 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/StringModule.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/StringModule.java
@@ -360,7 +360,7 @@
res.add(self.substring(start, end));
start = end + sep.length();
}
- return StarlarkList.wrapUnsafe(thread, res);
+ return StarlarkList.copyOf(thread.mutability(), res);
}
@SkylarkCallable(
@@ -410,7 +410,7 @@
end = start;
}
Collections.reverse(res);
- return StarlarkList.wrapUnsafe(thread, res);
+ return StarlarkList.copyOf(thread.mutability(), res);
}
@SkylarkCallable(
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Tuple.java b/src/main/java/com/google/devtools/build/lib/syntax/Tuple.java
index ea2004c..dbee242 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Tuple.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Tuple.java
@@ -15,9 +15,16 @@
package com.google.devtools.build.lib.syntax;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.ObjectArrays;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
+import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
+import java.util.AbstractCollection;
+import java.util.AbstractList;
+import java.util.Arrays;
+import java.util.Iterator;
import java.util.List;
/**
@@ -41,62 +48,70 @@
+ "('a', 'b', 'c', 'd')[::2] # ('a', 'c')\n"
+ "('a', 'b', 'c', 'd')[3:0:-1] # ('d', 'c', 'b')</pre>"
+ "Tuples are immutable, therefore <code>x[1] = \"a\"</code> is not supported.")
-public final class Tuple<E> extends Sequence<E> {
+public final class Tuple<E> extends AbstractList<E> implements Sequence<E> {
- private final ImmutableList<E> contents;
+ private final Object[] elems;
- private Tuple(ImmutableList<E> contents) {
- this.contents = contents;
+ private Tuple(Object[] elems) {
+ this.elems = elems;
}
- /**
- * A shared instance for the empty tuple.
- *
- * <p>This instance should be the only empty tuple.
- */
- private static final Tuple<?> EMPTY = new Tuple<>(ImmutableList.of());
+ // The shared (sole) empty tuple.
+ private static final Tuple<?> EMPTY = new Tuple<>(new Object[0]);
/** Returns the empty tuple, cast to have an arbitrary content type. */
@SuppressWarnings("unchecked")
public static <T> Tuple<T> empty() {
- return (Tuple<T>) EMPTY;
+ return (Tuple<T>) EMPTY; // unchecked
}
/**
- * Creates a {@code Tuple} from an {@link ImmutableList}, reusing the empty instance if
- * applicable.
+ * Returns a Tuple that wraps the specified array, which must not be subsequently modified. The
+ * caller is additionally trusted to choose an appropriate type T.
*/
- private static <T> Tuple<T> create(ImmutableList<T> contents) {
- if (contents.isEmpty()) {
+ static <T> Tuple<T> wrap(Object[] array) {
+ return array.length == 0 ? empty() : new Tuple<T>(array);
+ }
+
+ /** Returns a tuple containing the given elements. */
+ @SuppressWarnings("unchecked")
+ public static <T> Tuple<T> copyOf(Iterable<? extends T> seq) {
+ if (seq instanceof Tuple) {
+ return (Tuple<T>) seq; // unchecked
+ }
+ return wrap(Iterables.toArray(seq, Object.class));
+ }
+
+ /** Returns a tuple containing the given elements. */
+ public static <T> Tuple<T> of(T... elems) {
+ if (elems.length == 0) {
return empty();
}
- return new Tuple<>(contents);
+ return new Tuple<T>(Arrays.copyOf(elems, elems.length));
}
- /** Returns a {@code Tuple} whose items are given by an iterable. */
- public static <T> Tuple<T> copyOf(Iterable<? extends T> contents) {
- return create(ImmutableList.<T>copyOf(contents));
+ /** Returns a two-element tuple. */
+ public static <T> Tuple<T> pair(T a, T b) {
+ // Equivalent to of(a, b) but avoids variadic array allocation.
+ return wrap(new Object[] {a, b});
}
- /**
- * Returns a {@code Tuple} whose items are given by an immutable list.
- *
- * <p>This method is a specialization of a {@link #copyOf(Iterable)} that avoids an unnecessary
- * {@code copyOf} invocation.
- */
- public static <T> Tuple<T> copyOf(ImmutableList<T> contents) {
- return create(contents);
+ /** Returns a three-element tuple. */
+ public static <T> Tuple<T> triple(T a, T b, T c) {
+ // Equivalent to of(a, b, c) but avoids variadic array allocation.
+ return wrap(new Object[] {a, b, c});
}
- /** Returns a {@code Tuple} with the given items. */
- public static <T> Tuple<T> of(T... elements) {
- return Tuple.create(ImmutableList.copyOf(elements));
+ /** Returns a tuple that is the concatenation of two tuples. */
+ public static <T> Tuple<T> concat(Tuple<? extends T> x, Tuple<? extends T> y) {
+ // TODO(adonovan): opt: exploit x + () == x; y + () == y.
+ return wrap(ObjectArrays.concat(x.elems, y.elems, Object.class));
}
@Override
public boolean isImmutable() {
- for (Object item : this) {
- if (!EvalUtils.isImmutable(item)) {
+ for (Object x : elems) {
+ if (!EvalUtils.isImmutable(x)) {
return false;
}
}
@@ -105,8 +120,8 @@
@Override
public boolean isHashable() {
- for (Object item : this) {
- if (!EvalUtils.isHashable(item)) {
+ for (Object x : elems) {
+ if (!EvalUtils.isHashable(x)) {
return false;
}
}
@@ -114,49 +129,109 @@
}
@Override
- public ImmutableList<E> getImmutableList() {
- return contents;
+ public int hashCode() {
+ return 9857 + 8167 * Arrays.hashCode(elems);
}
@Override
- protected List<E> getContentsUnsafe() {
- return contents;
+ public boolean equals(Object that) {
+ // This slightly violates the java.util.List equivalence contract
+ // because it considers the class, not just the elements.
+ return this == that
+ || (that instanceof Tuple && Arrays.equals(this.elems, ((Tuple) that).elems));
}
- /** Returns a {@code Tuple} that is the concatenation of two {@code Tuple}s. */
- public static <T> Tuple<T> concat(Tuple<? extends T> left, Tuple<? extends T> right) {
- // Build the ImmutableList directly rather than use Iterables.concat, to avoid unnecessary
- // array resizing.
- return create(
- ImmutableList.<T>builderWithExpectedSize(left.size() + right.size())
- .addAll(left)
- .addAll(right)
- .build());
+ @Override
+ @SuppressWarnings("unchecked")
+ public E get(int i) {
+ return (E) elems[i]; // unchecked
+ }
+
+ @Override
+ public int size() {
+ return elems.length;
+ }
+
+ @Override
+ public Tuple<E> subList(int from, int to) {
+ return wrap(Arrays.copyOfRange(elems, from, to));
+ }
+
+ @Override
+ public Object[] toArray() {
+ return Arrays.copyOf(elems, elems.length);
+ }
+
+ @Override
+ public void repr(SkylarkPrinter printer) {
+ // TODO(adonovan): when SkylarkPrinter moves into this package,
+ // inline and simplify this, the sole call with isTuple=true.
+ printer.printList(this, /*isTuple=*/ true);
+ }
+
+ // TODO(adonovan): SkylarkValue has 3 String methods yet still we need this fourth. Why?
+ @Override
+ public String toString() {
+ return Printer.repr(this);
+ }
+
+ @Override
+ public ImmutableList<E> getImmutableList() {
+ // Share the array with this (immutable) Tuple.
+ return wrapImmutable(elems);
+ }
+
+ /**
+ * Returns a new ImmutableList<T> backed by {@code array}, which must not be subsequently
+ * modified.
+ */
+ // TODO(adonovan): move this somewhere more appropriate.
+ static <T> ImmutableList<T> wrapImmutable(Object[] array) {
+ // Construct an ImmutableList that shares the array.
+ // ImmutableList relies on the implementation of Collection.toArray
+ // not subsequently modifying the returned array.
+ return ImmutableList.copyOf(
+ new AbstractCollection<T>() {
+ @Override
+ public Object[] toArray() {
+ return array;
+ }
+
+ @Override
+ public int size() {
+ return array.length;
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ throw new UnsupportedOperationException();
+ }
+ });
}
@Override
public Tuple<E> getSlice(
Object start, Object end, Object step, Location loc, Mutability mutability)
throws EvalException {
- List<Integer> sliceIndices = EvalUtils.getSliceIndices(start, end, step, this.size(), loc);
- ImmutableList.Builder<E> builder = ImmutableList.builderWithExpectedSize(sliceIndices.size());
- // foreach is not used to avoid iterator overhead
- for (int i = 0; i < sliceIndices.size(); ++i) {
- builder.add(this.get(sliceIndices.get(i)));
+ // TODO(adonovan): opt: this is horribly inefficient.
+ List<Integer> indices = EvalUtils.getSliceIndices(start, end, step, this.size(), loc);
+ Object[] res = new Object[indices.size()];
+ for (int i = 0; i < indices.size(); ++i) {
+ res[i] = elems[indices.get(i)];
}
- return copyOf(builder.build());
+ return wrap(res);
}
- @Override
- public Tuple<E> repeat(int times, Mutability mutability) {
- if (times <= 0) {
+ /** Returns a Tuple containing n consecutive repeats of this tuple. */
+ public Tuple<E> repeat(int n, Mutability mutability) {
+ if (n <= 0 || isEmpty()) {
return empty();
}
-
- ImmutableList.Builder<E> builder = ImmutableList.builderWithExpectedSize(this.size() * times);
- for (int i = 0; i < times; i++) {
- builder.addAll(this);
+ // TODO(adonovan): reject unreasonably large n.
+ Object[] res = new Object[n * elems.length];
+ for (int i = 0; i < n; i++) {
+ System.arraycopy(elems, 0, res, i * elems.length, elems.length);
}
- return copyOf(builder.build());
+ return wrap(res);
}
}
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkListTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkListTest.java
index 8732de1..ce8ef3c 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkListTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkListTest.java
@@ -284,16 +284,14 @@
}
@Test
- public void testWrapUnsafeTakesOwnershipOfPassedArrayList() throws EvalException {
- ArrayList<String> wrapped = Lists.newArrayList("hi");
+ public void testWrapTakesOwnershipOfArray() throws EvalException {
+ String[] wrapped = {"hello"};
Mutability mutability = Mutability.create("test");
- StarlarkList<String> mutableList = StarlarkList.wrapUnsafe(mutability, wrapped);
+ StarlarkList<String> mutableList = StarlarkList.wrap(mutability, wrapped);
// Big no-no, but we're proving a point.
- wrapped.add("added1");
- mutableList.add("added2", /*loc=*/ null);
- assertThat(wrapped).containsExactly("hi", "added1", "added2").inOrder();
- assertThat(mutableList).containsExactly("hi", "added1", "added2").inOrder();
+ wrapped[0] = "goodbye";
+ assertThat(mutableList).containsExactly("goodbye");
}
@Test