Googler | cfd681f | 2019-11-11 07:24:02 -0800 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | package com.google.devtools.build.lib.syntax; |
| 16 | |
| 17 | import com.google.common.collect.ImmutableList; |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 18 | import com.google.common.collect.Iterables; |
| 19 | import com.google.common.collect.ObjectArrays; |
Googler | cfd681f | 2019-11-11 07:24:02 -0800 | [diff] [blame] | 20 | import com.google.devtools.build.lib.events.Location; |
| 21 | import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; |
| 22 | import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory; |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 23 | import java.util.AbstractCollection; |
| 24 | import java.util.AbstractList; |
| 25 | import java.util.Arrays; |
| 26 | import java.util.Iterator; |
Googler | cfd681f | 2019-11-11 07:24:02 -0800 | [diff] [blame] | 27 | import java.util.List; |
| 28 | |
| 29 | /** |
| 30 | * A Skylark tuple, i.e. the value represented by {@code (1, 2, 3)}. Tuples are always immutable |
| 31 | * (regardless of the {@link StarlarkThread} they are created in). |
| 32 | */ |
| 33 | @SkylarkModule( |
| 34 | name = "tuple", |
| 35 | category = SkylarkModuleCategory.BUILTIN, |
| 36 | doc = |
| 37 | "The built-in tuple type. Example tuple expressions:<br>" |
| 38 | + "<pre class=language-python>x = (1, 2, 3)</pre>" |
| 39 | + "Accessing elements is possible using indexing (starts from <code>0</code>):<br>" |
| 40 | + "<pre class=language-python>e = x[1] # e == 2</pre>" |
| 41 | + "Lists support the <code>+</code> operator to concatenate two tuples. Example:<br>" |
| 42 | + "<pre class=language-python>x = (1, 2) + (3, 4) # x == (1, 2, 3, 4)\n" |
| 43 | + "x = (\"a\", \"b\")\n" |
| 44 | + "x += (\"c\",) # x == (\"a\", \"b\", \"c\")</pre>" |
| 45 | + "Similar to lists, tuples support slice operations:" |
| 46 | + "<pre class=language-python>('a', 'b', 'c', 'd')[1:3] # ('b', 'c')\n" |
| 47 | + "('a', 'b', 'c', 'd')[::2] # ('a', 'c')\n" |
| 48 | + "('a', 'b', 'c', 'd')[3:0:-1] # ('d', 'c', 'b')</pre>" |
| 49 | + "Tuples are immutable, therefore <code>x[1] = \"a\"</code> is not supported.") |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 50 | public final class Tuple<E> extends AbstractList<E> implements Sequence<E> { |
Googler | cfd681f | 2019-11-11 07:24:02 -0800 | [diff] [blame] | 51 | |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 52 | private final Object[] elems; |
Googler | cfd681f | 2019-11-11 07:24:02 -0800 | [diff] [blame] | 53 | |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 54 | private Tuple(Object[] elems) { |
| 55 | this.elems = elems; |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 56 | } |
| 57 | |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 58 | // The shared (sole) empty tuple. |
| 59 | private static final Tuple<?> EMPTY = new Tuple<>(new Object[0]); |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 60 | |
| 61 | /** Returns the empty tuple, cast to have an arbitrary content type. */ |
| 62 | @SuppressWarnings("unchecked") |
| 63 | public static <T> Tuple<T> empty() { |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 64 | return (Tuple<T>) EMPTY; // unchecked |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 65 | } |
| 66 | |
| 67 | /** |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 68 | * Returns a Tuple that wraps the specified array, which must not be subsequently modified. The |
| 69 | * caller is additionally trusted to choose an appropriate type T. |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 70 | */ |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 71 | static <T> Tuple<T> wrap(Object[] array) { |
| 72 | return array.length == 0 ? empty() : new Tuple<T>(array); |
| 73 | } |
| 74 | |
| 75 | /** Returns a tuple containing the given elements. */ |
| 76 | @SuppressWarnings("unchecked") |
| 77 | public static <T> Tuple<T> copyOf(Iterable<? extends T> seq) { |
| 78 | if (seq instanceof Tuple) { |
| 79 | return (Tuple<T>) seq; // unchecked |
| 80 | } |
| 81 | return wrap(Iterables.toArray(seq, Object.class)); |
| 82 | } |
| 83 | |
| 84 | /** Returns a tuple containing the given elements. */ |
| 85 | public static <T> Tuple<T> of(T... elems) { |
| 86 | if (elems.length == 0) { |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 87 | return empty(); |
Googler | cfd681f | 2019-11-11 07:24:02 -0800 | [diff] [blame] | 88 | } |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 89 | return new Tuple<T>(Arrays.copyOf(elems, elems.length)); |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 90 | } |
Googler | cfd681f | 2019-11-11 07:24:02 -0800 | [diff] [blame] | 91 | |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 92 | /** Returns a two-element tuple. */ |
| 93 | public static <T> Tuple<T> pair(T a, T b) { |
| 94 | // Equivalent to of(a, b) but avoids variadic array allocation. |
| 95 | return wrap(new Object[] {a, b}); |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 96 | } |
Googler | cfd681f | 2019-11-11 07:24:02 -0800 | [diff] [blame] | 97 | |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 98 | /** Returns a three-element tuple. */ |
| 99 | public static <T> Tuple<T> triple(T a, T b, T c) { |
| 100 | // Equivalent to of(a, b, c) but avoids variadic array allocation. |
| 101 | return wrap(new Object[] {a, b, c}); |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 102 | } |
Googler | cfd681f | 2019-11-11 07:24:02 -0800 | [diff] [blame] | 103 | |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 104 | /** Returns a tuple that is the concatenation of two tuples. */ |
| 105 | public static <T> Tuple<T> concat(Tuple<? extends T> x, Tuple<? extends T> y) { |
| 106 | // TODO(adonovan): opt: exploit x + () == x; y + () == y. |
| 107 | return wrap(ObjectArrays.concat(x.elems, y.elems, Object.class)); |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 108 | } |
| 109 | |
| 110 | @Override |
| 111 | public boolean isImmutable() { |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 112 | for (Object x : elems) { |
| 113 | if (!EvalUtils.isImmutable(x)) { |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 114 | return false; |
Googler | cfd681f | 2019-11-11 07:24:02 -0800 | [diff] [blame] | 115 | } |
Googler | cfd681f | 2019-11-11 07:24:02 -0800 | [diff] [blame] | 116 | } |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 117 | return true; |
| 118 | } |
Googler | cfd681f | 2019-11-11 07:24:02 -0800 | [diff] [blame] | 119 | |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 120 | @Override |
| 121 | public boolean isHashable() { |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 122 | for (Object x : elems) { |
| 123 | if (!EvalUtils.isHashable(x)) { |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 124 | return false; |
Googler | cfd681f | 2019-11-11 07:24:02 -0800 | [diff] [blame] | 125 | } |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 126 | } |
| 127 | return true; |
| 128 | } |
| 129 | |
| 130 | @Override |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 131 | public int hashCode() { |
| 132 | return 9857 + 8167 * Arrays.hashCode(elems); |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 133 | } |
| 134 | |
| 135 | @Override |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 136 | public boolean equals(Object that) { |
| 137 | // This slightly violates the java.util.List equivalence contract |
| 138 | // because it considers the class, not just the elements. |
| 139 | return this == that |
| 140 | || (that instanceof Tuple && Arrays.equals(this.elems, ((Tuple) that).elems)); |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 141 | } |
| 142 | |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 143 | @Override |
| 144 | @SuppressWarnings("unchecked") |
| 145 | public E get(int i) { |
| 146 | return (E) elems[i]; // unchecked |
| 147 | } |
| 148 | |
| 149 | @Override |
| 150 | public int size() { |
| 151 | return elems.length; |
| 152 | } |
| 153 | |
| 154 | @Override |
| 155 | public Tuple<E> subList(int from, int to) { |
| 156 | return wrap(Arrays.copyOfRange(elems, from, to)); |
| 157 | } |
| 158 | |
| 159 | @Override |
| 160 | public Object[] toArray() { |
| 161 | return Arrays.copyOf(elems, elems.length); |
| 162 | } |
| 163 | |
| 164 | @Override |
Googler | 34f7058 | 2019-11-25 12:27:34 -0800 | [diff] [blame^] | 165 | public void repr(Printer printer) { |
| 166 | // TODO(adonovan): when Printer moves into this package, |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 167 | // inline and simplify this, the sole call with isTuple=true. |
| 168 | printer.printList(this, /*isTuple=*/ true); |
| 169 | } |
| 170 | |
Googler | 34f7058 | 2019-11-25 12:27:34 -0800 | [diff] [blame^] | 171 | // TODO(adonovan): StarlarkValue has 3 String methods yet still we need this fourth. Why? |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 172 | @Override |
| 173 | public String toString() { |
Googler | b1e232d | 2019-11-22 15:29:43 -0800 | [diff] [blame] | 174 | return Starlark.repr(this); |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 175 | } |
| 176 | |
| 177 | @Override |
| 178 | public ImmutableList<E> getImmutableList() { |
| 179 | // Share the array with this (immutable) Tuple. |
| 180 | return wrapImmutable(elems); |
| 181 | } |
| 182 | |
| 183 | /** |
| 184 | * Returns a new ImmutableList<T> backed by {@code array}, which must not be subsequently |
| 185 | * modified. |
| 186 | */ |
| 187 | // TODO(adonovan): move this somewhere more appropriate. |
| 188 | static <T> ImmutableList<T> wrapImmutable(Object[] array) { |
| 189 | // Construct an ImmutableList that shares the array. |
| 190 | // ImmutableList relies on the implementation of Collection.toArray |
| 191 | // not subsequently modifying the returned array. |
| 192 | return ImmutableList.copyOf( |
| 193 | new AbstractCollection<T>() { |
| 194 | @Override |
| 195 | public Object[] toArray() { |
| 196 | return array; |
| 197 | } |
| 198 | |
| 199 | @Override |
| 200 | public int size() { |
| 201 | return array.length; |
| 202 | } |
| 203 | |
| 204 | @Override |
| 205 | public Iterator<T> iterator() { |
| 206 | throw new UnsupportedOperationException(); |
| 207 | } |
| 208 | }); |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 209 | } |
| 210 | |
| 211 | @Override |
| 212 | public Tuple<E> getSlice( |
| 213 | Object start, Object end, Object step, Location loc, Mutability mutability) |
| 214 | throws EvalException { |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 215 | // TODO(adonovan): opt: this is horribly inefficient. |
| 216 | List<Integer> indices = EvalUtils.getSliceIndices(start, end, step, this.size(), loc); |
| 217 | Object[] res = new Object[indices.size()]; |
| 218 | for (int i = 0; i < indices.size(); ++i) { |
| 219 | res[i] = elems[indices.get(i)]; |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 220 | } |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 221 | return wrap(res); |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 222 | } |
| 223 | |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 224 | /** Returns a Tuple containing n consecutive repeats of this tuple. */ |
| 225 | public Tuple<E> repeat(int n, Mutability mutability) { |
| 226 | if (n <= 0 || isEmpty()) { |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 227 | return empty(); |
Googler | cfd681f | 2019-11-11 07:24:02 -0800 | [diff] [blame] | 228 | } |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 229 | // TODO(adonovan): reject unreasonably large n. |
| 230 | Object[] res = new Object[n * elems.length]; |
| 231 | for (int i = 0; i < n; i++) { |
| 232 | System.arraycopy(elems, 0, res, i * elems.length, elems.length); |
Googler | cfd681f | 2019-11-11 07:24:02 -0800 | [diff] [blame] | 233 | } |
Googler | ccdeeda | 2019-11-15 10:00:34 -0800 | [diff] [blame] | 234 | return wrap(res); |
Googler | 1b80d57 | 2019-11-14 11:01:25 -0800 | [diff] [blame] | 235 | } |
Googler | cfd681f | 2019-11-11 07:24:02 -0800 | [diff] [blame] | 236 | } |