blob: 1ae08f9c201dd90194fc3fad7b445a9a308a488d [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
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 com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
import com.google.devtools.build.lib.skylarkinterface.StarlarkContext;
import com.google.devtools.build.lib.syntax.StarlarkMutable.BaseMutableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.RandomAccess;
import javax.annotation.Nullable;
/**
* A Skylark list or tuple.
*
* <p>Although this implements the {@link List} interface, it is not mutable via that interface's
* methods. Instead, use the mutators that take in a {@link Mutability} object.
*/
@SkylarkModule(
name = "sequence",
documented = false,
category = SkylarkModuleCategory.BUILTIN,
doc = "common type of lists and tuples."
)
public abstract class SkylarkList<E> extends BaseMutableList<E>
implements List<E>, RandomAccess, SkylarkIndexable {
/** Returns true if this list is a Skylark tuple. */
public abstract boolean isTuple();
/**
* Returns an ImmutableList object with the current underlying contents of this SkylarkList.
*/
public abstract ImmutableList<E> getImmutableList();
/**
* Retrieve an entry from a SkylarkList.
*
* @param key the index
* @param loc a {@link Location} in case of error
* @throws EvalException if the key is invalid
*/
@Override
public E getIndex(Object key, Location loc, StarlarkContext context) throws EvalException {
List<E> list = getContentsUnsafe();
int index = EvalUtils.getSequenceIndex(key, list.size(), loc);
return list.get(index);
}
@Override
public boolean containsKey(Object key, Location loc, StarlarkContext context)
throws EvalException {
for (Object obj : this) {
if (obj.equals(key)) {
return true;
}
}
return false;
}
/**
* Constructs a version of this {@code SkylarkList} containing just the items in a slice.
*
* <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.
*
* @see EvalUtils#getSliceIndices
* @throws EvalException if the key is invalid; uses {@code loc} for error reporting
*/
public abstract SkylarkList<E> getSlice(
Object start, Object end, Object step, Location loc, Mutability mutability)
throws EvalException;
/**
* Constructs a repetition of this {@code SkylarkList}.
*
* <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.
*/
public abstract SkylarkList<E> repeat(int times, Mutability mutability);
@Override
public void repr(SkylarkPrinter printer) {
printer.printList(getContentsUnsafe(), isTuple());
}
@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 SkylarkList .equals() an arbitrary List with same contents.
// This is because we use .equals() to model skylark equality, which like Python
// distinguishes a MutableList from a Tuple.
@Override
public boolean equals(Object object) {
return (this == object)
|| ((object != null)
&& (this.getClass() == object.getClass())
&& getContentsUnsafe().equals(((SkylarkList) 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}.
*
* <p>The returned list may or may not be a view that is affected by updates to the original list.
*
* @param list the original list to cast
* @param type the expected type of all the list's elements
* @param description a description of the argument being converted, or null, for debugging
*/
// 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.
@SuppressWarnings("unchecked")
public static <T> List<T> castList(List<?> list, Class<T> type, @Nullable String description)
throws EvalException {
Object desc = description == null ? null : Printer.formattable("'%s' element", description);
for (Object value : list) {
SkylarkType.checkType(value, type, desc);
}
return Collections.unmodifiableList((List<T>) list);
}
/**
* If {@code obj} is a {@code SkylarkList}, casts it to an unmodifiable {@code List<T>} after
* checking that each element has type {@code type}. If {@code obj} is {@code None} or null,
* treats it as an empty list. For all other values, throws an {@link EvalException}.
*
* <p>The returned list may or may not be a view that is affected by updates to the original list.
*
* @param obj the object to cast. null and None are treated as an empty list.
* @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 static <T> List<T> castSkylarkListOrNoneToList(
Object obj, Class<T> type, @Nullable String description)
throws EvalException {
if (EvalUtils.isNullOrNone(obj)) {
return ImmutableList.of();
}
if (obj instanceof SkylarkList) {
return ((SkylarkList<?>) obj).getContents(type, description);
}
throw new EvalException(null,
String.format("Illegal argument: %s is not of expected type list or NoneType",
description == null ? Printer.repr(obj) : String.format("'%s'", description)));
}
/**
* 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)
throws EvalException {
return castList(getContentsUnsafe(), 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 MutableList} or {@link Tuple}.
*
* <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 MutableList factory method. With
// such a method, we may no longer need to take null as a possible value for the Mutability or
// Environment. That in turn would allow us to overload MutableList#of to take either a Mutability
// or Environment.
public static <E> SkylarkList<E> createImmutable(Iterable<? extends E> contents) {
return MutableList.copyOf(Mutability.IMMUTABLE, contents);
}
/**
* A Skylark list, i.e., the value represented by {@code [1, 2, 3]}. Lists are mutable datatypes.
*/
@SkylarkModule(
name = "list",
category = SkylarkModuleCategory.BUILTIN,
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>"
+ "Similar to strings, lists support slice operations:"
+ "<pre class=language-python>['a', 'b', 'c', 'd'][1:3] # ['b', 'c']\n"
+ "['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 static final class MutableList<E> extends SkylarkList<E> {
private final ArrayList<E> contents;
/** Final except for {@link #unsafeShallowFreeze}; must not be modified any other way. */
private Mutability mutability;
private MutableList(
ArrayList<E> rawContents,
@Nullable Mutability mutability) {
this.contents = Preconditions.checkNotNull(rawContents);
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).
*/
static <T> MutableList<T> wrapUnsafe(@Nullable Environment env, ArrayList<T> rawContents) {
return wrapUnsafe(env == null ? null : env.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> MutableList<T> wrapUnsafe(
@Nullable Mutability mutability, ArrayList<T> rawContents) {
return new MutableList<>(rawContents, mutability);
}
/**
* A shared instance for the empty list with immutable mutability.
*
* <p>Other immutable empty list objects can exist, e.g. lists that were once mutable but whose
* environments were then frozen. This instance is for empty lists that were always frozen from
* the beginning.
*/
private static final MutableList<?> EMPTY =
MutableList.copyOf(Mutability.IMMUTABLE, ImmutableList.of());
/** Returns an empty frozen list, cast to have an arbitrary content type. */
@SuppressWarnings("unchecked")
public static <T> MutableList<T> empty() {
return (MutableList<T>) EMPTY;
}
/**
* Returns a {@code MutableList} whose items are given by an iterable and which has the given
* {@link Mutability}. If {@code mutability} is null, the list is immutable.
*/
public static <T> MutableList<T> copyOf(
@Nullable Mutability mutability, Iterable<? extends T> contents) {
return new MutableList<>(
Lists.newArrayList(contents),
mutability);
}
/**
* Returns a {@code MutableList} whose items are given by an iterable and which has the {@link
* Mutability} belonging to the given {@link Environment}. If {@code env} is null, the list is
* immutable.
*/
public static <T> MutableList<T> copyOf(
@Nullable Environment env, Iterable<? extends T> contents) {
return MutableList.copyOf(
env == null ? null : env.mutability(),
contents);
}
/**
* Returns a {@code MutableList} with the given items and the {@link Mutability} of the given
* {@link Environment}. If {@code env} is null, the list is immutable.
*/
public static <T> MutableList<T> of(@Nullable Environment env, T... contents) {
// Safe since we're taking a copy of the input.
return MutableList.wrapUnsafe(
env == null ? null : env.mutability(), Lists.newArrayList(contents));
}
@Override
public Mutability mutability() {
return mutability;
}
@Override
public void unsafeShallowFreeze() {
Mutability.Freezable.checkUnsafeShallowFreezePrecondition(this);
this.mutability = Mutability.IMMUTABLE;
}
@Override
public boolean isTuple() {
return false;
}
@Override
public ImmutableList<E> getImmutableList() {
return ImmutableList.copyOf(contents);
}
@Override
protected List<E> getContentsUnsafe() {
return contents;
}
/**
* Returns a new {@code MutableList} that is the concatenation of two {@code MutableList}s. The
* new list will have the given {@link Mutability}.
*/
public static <T> MutableList<T> concat(
MutableList<? extends T> left,
MutableList<? extends T> right,
Mutability mutability) {
ArrayList<T> newContents = new ArrayList<>(left.size() + right.size());
addAll(newContents, left.contents);
addAll(newContents, right.contents);
return new MutableList<>(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));
}
}
@Override
public MutableList<E> repeat(int times, Mutability mutability) {
if (times <= 0) {
return MutableList.wrapUnsafe(mutability, new ArrayList<>());
}
ArrayList<E> repeated = new ArrayList<>(this.size() * times);
for (int i = 0; i < times; i++) {
repeated.addAll(this);
}
return MutableList.wrapUnsafe(mutability, repeated);
}
@Override
public MutableList<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)));
}
return MutableList.wrapUnsafe(mutability, list);
}
/**
* Appends an element to the end of the list, after validating that mutation is allowed.
*
* @param element the element to add
* @param loc the location to use for error reporting
* @param mutability the {@link Mutability} associated with the operation
*/
public void add(E element, Location loc, Mutability mutability) throws EvalException {
checkMutable(loc, mutability);
contents.add(element);
}
/**
* Inserts an element at a given position to the list.
*
* @param index the new element's index
* @param element the element to add
* @param loc the location to use for error reporting
* @param mutability the {@link Mutability} associated with the operation
*/
public void add(int index, E element, Location loc, Mutability mutability)
throws EvalException {
checkMutable(loc, mutability);
contents.add(index, element);
}
/**
* Appends all the elements to the end of the list.
*
* @param elements the elements to add
* @param loc the location to use for error reporting
* @param mutability the {@link Mutability} associated with the operation
*/
public void addAll(Iterable<? extends E> elements, Location loc, Mutability mutability)
throws EvalException {
checkMutable(loc, mutability);
Iterables.addAll(contents, elements);
}
/**
* Removes the element at a given index. The index must already have been validated to be in
* range.
*
* @param index the index of the element to remove
* @param loc the location to use for error reporting
* @param mutability the {@link Mutability} associated with the operation
*/
public void remove(int index, Location loc, Mutability mutability) throws EvalException {
checkMutable(loc, mutability);
contents.remove(index);
}
@SkylarkCallable(
name = "remove",
doc =
"Removes the first item from the list whose value is x. "
+ "It is an error if there is no such item.",
parameters = {
@Param(name = "x", type = Object.class, doc = "The object to remove.")
},
useLocation = true,
useEnvironment = true
)
public Runtime.NoneType removeObject(Object x, Location loc, Environment env)
throws EvalException {
for (int i = 0; i < size(); i++) {
if (get(i).equals(x)) {
remove(i, loc, env.mutability());
return Runtime.NONE;
}
}
throw new EvalException(loc, Printer.format("item %r not found in list", x));
}
/**
* Sets the position at the given index to contain the given value. The index must already have
* been validated to be in range.
*
* @param index the position to change
* @param value the new value
* @param loc the location to use for error reporting
* @param mutability the {@link Mutability} associated with the operation
*/
public void set(int index, E value, Location loc, Mutability mutability) throws EvalException {
checkMutable(loc, mutability);
contents.set(index, value);
}
@SkylarkCallable(
name = "append",
doc = "Adds an item to the end of the list.",
parameters = {
@Param(name = "item",
type = Object.class,
doc = "Item to add at the end.",
noneable = true)
},
useLocation = true,
useEnvironment = true
)
public Runtime.NoneType append(
E item, Location loc, Environment env)
throws EvalException {
add(item, loc, env.mutability());
return Runtime.NONE;
}
@SkylarkCallable(
name = "clear",
doc = "Removes all the elements of the list.",
useLocation = true,
useEnvironment = true)
public Runtime.NoneType clearMethod(Location loc, Environment env) throws EvalException {
checkMutable(loc, env.mutability());
contents.clear();
return Runtime.NONE;
}
@SkylarkCallable(
name = "insert",
doc = "Inserts an item at a given position.",
parameters = {
@Param(name = "index", type = Integer.class, doc = "The index of the given position."),
@Param(name = "item", type = Object.class, doc = "The item.", noneable = true)
},
useLocation = true,
useEnvironment = true
)
public Runtime.NoneType insert(
Integer index, E item, Location loc, Environment env)
throws EvalException {
add(EvalUtils.clampRangeEndpoint(index, size()), item, loc, env.mutability());
return Runtime.NONE;
}
@SkylarkCallable(
name = "extend",
doc = "Adds all items to the end of the list.",
parameters = {
@Param(name = "items", type = SkylarkList.class, doc = "Items to add at the end.")
},
useLocation = true,
useEnvironment = true
)
public Runtime.NoneType extend(
SkylarkList<E> items, Location loc, Environment env)
throws EvalException {
addAll(items, loc, env.mutability());
return Runtime.NONE;
}
@SkylarkCallable(
name = "index",
doc =
"Returns the index in the list of the first item whose value is x. "
+ "It is an error if there is no such item.",
parameters = {
@Param(name = "x", type = Object.class, doc = "The object to search."),
@Param(
name = "start",
type = Integer.class,
defaultValue = "None",
noneable = true,
named = true,
doc = "The start index of the list portion to inspect."),
@Param(
name = "end",
type = Integer.class,
defaultValue = "None",
noneable = true,
named = true,
doc = "The end index of the list portion to inspect.")
},
useLocation = true)
public Integer index(Object x, Object start, Object end, Location loc) throws EvalException {
int i =
start == Runtime.NONE ? 0 : EvalUtils.clampRangeEndpoint((Integer) start, this.size());
int j =
end == Runtime.NONE
? this.size()
: EvalUtils.clampRangeEndpoint((Integer) end, this.size());
while (i < j) {
if (this.get(i).equals(x)) {
return i;
}
i++;
}
throw new EvalException(loc, Printer.format("item %r not found in list", x));
}
@SkylarkCallable(
name = "pop",
doc =
"Removes the item at the given position in the list, and returns it. "
+ "If no <code>index</code> is specified, "
+ "it removes and returns the last item in the list.",
parameters = {
@Param(
name = "i",
type = Integer.class,
noneable = true,
defaultValue = "None",
doc = "The index of the item."
)
},
useLocation = true,
useEnvironment = true
)
public Object pop(Object i, Location loc, Environment env)
throws EvalException {
int arg = i == Runtime.NONE ? -1 : (Integer) i;
int index = EvalUtils.getSequenceIndex(arg, size(), loc);
Object result = get(index);
remove(index, loc, env.mutability());
return result;
}
}
/**
* A Skylark tuple, i.e. the value represented by {@code (1, 2, 3)}. Tuples are always immutable
* (regardless of the {@link Environment} they are created in).
*/
@SkylarkModule(
name = "tuple",
category = SkylarkModuleCategory.BUILTIN,
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>"
+ "Similar to lists, tuples support slice operations:"
+ "<pre class=language-python>('a', 'b', 'c', 'd')[1:3] # ('b', 'c')\n"
+ "('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 static final class Tuple<E> extends SkylarkList<E> {
private final ImmutableList<E> contents;
private Tuple(ImmutableList<E> contents) {
this.contents = contents;
}
/**
* 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());
/** Returns the empty tuple, cast to have an arbitrary content type. */
@SuppressWarnings("unchecked")
public static <T> Tuple<T> empty() {
return (Tuple<T>) EMPTY;
}
/**
* Creates a {@code Tuple} from an {@link ImmutableList}, reusing the empty instance if
* applicable.
*/
private static <T> Tuple<T> create(ImmutableList<T> contents) {
if (contents.isEmpty()) {
return empty();
}
return new Tuple<>(contents);
}
/** 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 {@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 {@code Tuple} with the given items. */
public static <T> Tuple<T> of(T... elements) {
return Tuple.create(ImmutableList.copyOf(elements));
}
// Overridden to recurse over children, since tuples use SHALLOW_IMMUTABLE and other
// StarlarkMutable subclasses do not.
@Override
public boolean isImmutable() {
for (Object item : this) {
if (!EvalUtils.isImmutable(item)) {
return false;
}
}
return true;
}
@Override
public Mutability mutability() {
return Mutability.SHALLOW_IMMUTABLE;
}
@Override
public boolean isTuple() {
return true;
}
@Override
public ImmutableList<E> getImmutableList() {
return contents;
}
@Override
protected List<E> getContentsUnsafe() {
return contents;
}
/** 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
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)));
}
return copyOf(builder.build());
}
@Override
public Tuple<E> repeat(int times, Mutability mutability) {
if (times <= 0) {
return empty();
}
ImmutableList.Builder<E> builder = ImmutableList.builderWithExpectedSize(this.size() * times);
for (int i = 0; i < times; i++) {
builder.addAll(this);
}
return copyOf(builder.build());
}
}
}