// 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.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; import com.google.devtools.build.lib.syntax.SkylarkMutable.MutableCollection; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; import javax.annotation.Nullable; /** * A class to handle lists and tuples in Skylark. */ @SkylarkModule(name = "sequence", documented = false, doc = "common type of lists and tuples") public abstract class SkylarkList extends MutableCollection implements List, RandomAccess { /** * Returns an ImmutableList object with the current underlying contents of this SkylarkList. */ public abstract ImmutableList getImmutableList(); /** * Returns a List object with the current underlying contents of this SkylarkList. * This object must not be mutated, but need not be an {@link ImmutableList}. * Indeed it can sometimes be a {@link GlobList}. */ // TODO(bazel-team): move GlobList out of Skylark, into an extension. public abstract List getContents(); /** * The underlying contents are a (usually) mutable data structure. * Read access is forwarded to these contents. * This object must not be modified outside an {@link Environment} * with a correct matching {@link Mutability}, * which should be checked beforehand using {@link #checkMutable}. * it need not be an instance of {@link com.google.common.collect.ImmutableList}. */ @Override protected abstract List getContentsUnsafe(); /** * Returns true if this list is a tuple. */ public abstract boolean isTuple(); // A SkylarkList forwards all read-only access to the getContentsUnsafe(). @Override public final E get(int i) { return getContentsUnsafe().get(i); } @Override public int indexOf(Object element) { return getContentsUnsafe().indexOf(element); } @Override public int lastIndexOf(Object element) { return getContentsUnsafe().lastIndexOf(element); } @Override public ListIterator listIterator() { return getContentsUnsafe().listIterator(); } @Override public ListIterator listIterator(int index) { return getContentsUnsafe().listIterator(index); } // For subList, use the immutable getContents() rather than getContentsUnsafe, // to prevent subsequent mutation. To get a mutable SkylarkList, // use a method that takes an Environment into account. @Override public List subList(int fromIndex, int toIndex) { return getContents().subList(fromIndex, toIndex); } // A SkylarkList disables all direct mutation methods. @Override public void add(int index, E element) { throw new UnsupportedOperationException(); } @Override public boolean addAll(int index, Collection elements) { throw new UnsupportedOperationException(); } @Override public E remove(int index) { throw new UnsupportedOperationException(); } @Override public E set(int index, E element) { throw new UnsupportedOperationException(); } // Other methods @Override public void write(Appendable buffer, char quotationMark) { Printer.printList(buffer, getContentsUnsafe(), isTuple(), quotationMark); } // 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) || ((this.getClass() == object.getClass()) && getContentsUnsafe().equals(((SkylarkList) object).getContentsUnsafe())); } @Override public int hashCode() { return getClass().hashCode() + 31 * getContentsUnsafe().hashCode(); } /** * Cast a {@code List} to a {@code List} after checking its current contents. * @param list the List to cast * @param type the expected class of elements * @param description a description of the argument being converted, or null, for debugging */ @SuppressWarnings("unchecked") public static List castList( List list, Class 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 (List) list; } /** * Cast a SkylarkList to a {@code List} after checking its current contents. * Treat None as meaning the empty List. * @param obj the Object to cast. null and None are treated as an empty list. * @param type the expected class of elements * @param description a description of the argument being converted, or null, for debugging */ public static List castSkylarkListOrNoneToList( Object obj, Class 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, Printer.format("Illegal argument: %s is not of expected type list or NoneType", description == null ? Printer.repr(obj) : String.format("'%s'", description))); } /** * Cast the SkylarkList object into a List of the given type. * @param type the expected class of elements * @param description a description of the argument being converted, or null, for debugging */ public List getContents(Class type, @Nullable String description) throws EvalException { return castList(getContentsUnsafe(), type, description); } /** * A class for mutable lists. */ @SkylarkModule( name = "list", doc = "A language built-in type to support lists. Example of list literal:
" + "
x = [1, 2, 3]
" + "Accessing elements is possible using indexing (starts from 0):
" + "
e = x[1]   # e == 2
" + "Lists support the + operator to concatenate two lists. Example:
" + "
x = [1, 2] + [3, 4]   # x == [1, 2, 3, 4]\n"
            + "x = [\"a\", \"b\"]\n"
            + "x += [\"c\"]            # x == [\"a\", \"b\", \"c\"]
" + "Similar to strings, lists support slice operations:" + "
['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']
" + "Lists are mutable, as in Python." ) public static final class MutableList extends SkylarkList { private final ArrayList contents = new ArrayList<>(); // Treat GlobList specially: external code depends on it. // TODO(bazel-team): make data structures *and binary operators* extensible // (via e.g. interface classes for each binary operator) so that GlobList // can be implemented outside of the core of Skylark. @Nullable private GlobList globList; 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 */ @SuppressWarnings("unchecked") MutableList(Iterable contents, Mutability mutability) { super(); addAllUnsafe(contents); if (contents instanceof GlobList) { globList = (GlobList) 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. * @param contents the contents of the list * @return an actually immutable MutableList containing the elements */ public MutableList(Iterable contents) { this(contents, Mutability.IMMUTABLE); } /** * Creates a mutable or immutable MutableList depending on the given {@link Mutability}. */ public MutableList(Mutability mutability) { this(Collections.EMPTY_LIST, mutability); } /** * Builds a Skylark list (actually immutable) from a variable number of arguments. * @param env an Environment from which to inherit Mutability, or null for immutable * @param contents the contents of the list * @return a Skylark list containing the specified arguments as elements. */ public static MutableList of(@Nullable Environment env, E... contents) { return new MutableList(ImmutableList.copyOf(contents), env); } /** * Adds all the elements at the end of the MutableList. * @param elements the elements to add * Assumes that you already checked for Mutability. */ private void addAllUnsafe(Iterable elements) { Iterables.addAll(contents, elements); } @Override protected void checkMutable(Location loc, Environment env) throws EvalException { super.checkMutable(loc, env); globList = null; // If you're going to mutate it, invalidate the underlying GlobList. } @Nullable public GlobList getGlobList() { return globList; } /** * @return the GlobList if there is one, otherwise an Immutable copy of the regular contents. */ @Override @SuppressWarnings("unchecked") public List getContents() { if (globList != null) { return globList; } return getImmutableList(); } @Override protected List getContentsUnsafe() { return contents; } /** * @return the GlobList if there is one, otherwise the regular contents. */ private List getGlobListOrContentsUnsafe() { if (globList != null) { return globList; } return contents; } /** * Concatenate two MutableList * @param left the start of the new list * @param right the end of the new list * @param env the Environment in which to create a new list * @return a new MutableList */ public static MutableList concat( MutableList left, MutableList right, Environment env) { if (left.getGlobList() == null && right.getGlobList() == null) { return new MutableList(Iterables.concat(left, right), env); } return new MutableList(GlobList.concat( left.getGlobListOrContentsUnsafe(), right.getGlobListOrContentsUnsafe()), env); } /** * 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(E element, Location loc, Environment env) throws EvalException { checkMutable(loc, env); contents.add(element); } /** * Inserts an item at a given position to the MutableList. * @param index the index of the given position * @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(int index, E element, Location loc, Environment env) throws EvalException { checkMutable(loc, env); contents.add(index, element); } public void remove(int index, Location loc, Environment env) throws EvalException { checkMutable(loc, env); contents.remove(index); } /** * 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); addAllUnsafe(elements); } @Override public ImmutableList getImmutableList() { return ImmutableList.copyOf(contents); } @Override public Mutability mutability() { return mutability; } @Override 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:
" + "
x = (1, 2, 3)
" + "Accessing elements is possible using indexing (starts from 0):
" + "
e = x[1]   # e == 2
" + "Lists support the + operator to concatenate two tuples. Example:
" + "
x = (1, 2) + (3, 4)   # x == (1, 2, 3, 4)\n"
            + "x = (\"a\", \"b\")\n"
            + "x += (\"c\",)            # x == (\"a\", \"b\", \"c\")
" + "Similar to lists, tuples support slice operations:" + "
('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')
" + "Tuples are immutable, therefore x[1] = \"a\" is not supported." ) @Immutable public static final class Tuple extends SkylarkList { private final ImmutableList contents; private Tuple(ImmutableList contents) { super(); this.contents = contents; } @Override public Mutability mutability() { return Mutability.IMMUTABLE; } /** * THE empty Skylark tuple. */ private static final Tuple EMPTY = new Tuple<>(ImmutableList.of()); @SuppressWarnings("unchecked") public static final Tuple empty() { return (Tuple) EMPTY; } /** * Creates a Tuple from an ImmutableList. */ public static Tuple create(ImmutableList 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(E... elements) { return Tuple.create(ImmutableList.copyOf(elements)); } @Override public ImmutableList getImmutableList() { return contents; } @Override public List getContents() { return contents; } @Override protected List getContentsUnsafe() { return contents; } @Override public boolean isTuple() { return true; } @Override public boolean isImmutable() { for (Object item : this) { if (!EvalUtils.isImmutable(item)) { return false; } } return true; } } }