blob: 8dfe097abfd987db3661ec873a6e1ff637db7f60 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
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.
14package com.google.devtools.build.lib.syntax;
15
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010016import com.google.common.collect.ImmutableList;
17import com.google.common.collect.ImmutableMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010018import com.google.common.collect.Iterables;
Vladimir Moskva76e31d12016-12-05 16:28:37 +000019import com.google.common.collect.Lists;
Laurent Le Brun196c1a72015-03-18 13:03:04 +000020import com.google.common.collect.Ordering;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010021import com.google.devtools.build.lib.collect.nestedset.NestedSet;
22import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
23import com.google.devtools.build.lib.events.Location;
Jon Brandvein1171a7f2016-10-11 17:51:06 +000024import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils;
John Field585d1a02015-12-16 16:03:52 +000025import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
26import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
Mark Schaller6df81792015-12-10 18:47:47 +000027import com.google.devtools.build.lib.util.Preconditions;
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +000028import com.google.devtools.build.lib.vfs.PathFragment;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010029import java.util.Collection;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030import java.util.List;
31import java.util.Map;
laurentlbc9b6f4a2017-06-21 11:58:50 +020032import javax.annotation.Nullable;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010033
34/**
35 * Utilities used by the evaluator.
36 */
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +000037public final class EvalUtils {
38
39 private EvalUtils() {}
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010040
Laurent Le Brunb54221c2015-04-16 16:14:37 +000041 /**
42 * The exception that SKYLARK_COMPARATOR might throw. This is an unchecked exception
43 * because Comparator doesn't let us declare exceptions. It should normally be caught
44 * and wrapped in an EvalException.
45 */
46 public static class ComparisonException extends RuntimeException {
47 public ComparisonException(String msg) {
48 super(msg);
49 }
50 }
51
52 /**
53 * Compare two Skylark objects.
54 *
Vladimir Moskvad200daf2016-12-23 16:35:37 +000055 * <p>It may throw an unchecked exception ComparisonException that should be wrapped in an
56 * EvalException.
Laurent Le Brunb54221c2015-04-16 16:14:37 +000057 */
Vladimir Moskvad200daf2016-12-23 16:35:37 +000058 public static final Ordering<Object> SKYLARK_COMPARATOR =
59 new Ordering<Object>() {
60 private int compareLists(SkylarkList o1, SkylarkList o2) {
61 for (int i = 0; i < Math.min(o1.size(), o2.size()); i++) {
62 int cmp = compare(o1.get(i), o2.get(i));
63 if (cmp != 0) {
64 return cmp;
65 }
66 }
67 return Integer.compare(o1.size(), o2.size());
Laurent Le Brunb54221c2015-04-16 16:14:37 +000068 }
Laurent Le Brunb54221c2015-04-16 16:14:37 +000069
Vladimir Moskvad200daf2016-12-23 16:35:37 +000070 @Override
71 @SuppressWarnings("unchecked")
72 public int compare(Object o1, Object o2) {
73 o1 = SkylarkType.convertToSkylark(o1, /*env=*/ null);
74 o2 = SkylarkType.convertToSkylark(o2, /*env=*/ null);
Laurent Le Brunb54221c2015-04-16 16:14:37 +000075
Vladimir Moskvad200daf2016-12-23 16:35:37 +000076 if (o1 instanceof SkylarkList
77 && o2 instanceof SkylarkList
78 && ((SkylarkList) o1).isTuple() == ((SkylarkList) o2).isTuple()) {
79 return compareLists((SkylarkList) o1, (SkylarkList) o2);
80 }
Vladimir Moskva7f0cd622017-02-16 13:48:37 +000081 if (!o1.getClass().equals(o2.getClass())) {
82 throw new ComparisonException(
83 "Cannot compare " + getDataTypeName(o1) + " with " + getDataTypeName(o2));
84 }
85
86 if (o1 instanceof ClassObject) {
87 throw new ComparisonException("Cannot compare structs");
88 }
89 if (o1 instanceof SkylarkNestedSet) {
90 throw new ComparisonException("Cannot compare depsets");
91 }
Vladimir Moskvad200daf2016-12-23 16:35:37 +000092 try {
93 return ((Comparable<Object>) o1).compareTo(o2);
94 } catch (ClassCastException e) {
Vladimir Moskva7f0cd622017-02-16 13:48:37 +000095 throw new ComparisonException(
96 "Cannot compare " + getDataTypeName(o1) + " with " + getDataTypeName(o2));
97 }
98 }
99 };
100
101 /**
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000102 * Checks that an Object is a valid key for a Skylark dict.
103 * @param o an Object to validate
Lukacs Berkiffa73ad2015-09-18 11:40:12 +0000104 * @throws EvalException if o is not a valid key
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100105 */
Klaas Boesche53de62a2015-11-06 15:12:10 +0000106 public static void checkValidDictKey(Object o) throws EvalException {
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000107 // TODO(bazel-team): check that all recursive elements are both Immutable AND Comparable.
108 if (isImmutable(o)) {
109 return;
110 }
111 // Same error message as Python (that makes it a TypeError).
112 throw new EvalException(null, Printer.format("unhashable type: '%r'", o.getClass()));
113 }
114
115 /**
116 * Is this object known or assumed to be recursively immutable by Skylark?
117 * @param o an Object
118 * @return true if the object is known to be an immutable value.
119 */
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000120 // NB: This is used as the basis for accepting objects in SkylarkNestedSet-s,
121 // as well as for accepting objects as keys for Skylark dict-s.
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000122 public static boolean isImmutable(Object o) {
123 if (o instanceof SkylarkValue) {
124 return ((SkylarkValue) o).isImmutable();
125 }
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000126 return isImmutable(o.getClass());
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000127 }
128
129 /**
130 * Is this class known to be *recursively* immutable by Skylark?
131 * For instance, class Tuple is not it, because it can contain mutable values.
132 * @param c a Class
133 * @return true if the class is known to represent only recursively immutable values.
134 */
135 // NB: This is used as the basis for accepting objects in SkylarkNestedSet-s,
136 // as well as for accepting objects as keys for Skylark dict-s.
137 static boolean isImmutable(Class<?> c) {
138 return c.isAnnotationPresent(Immutable.class) // TODO(bazel-team): beware of containers!
139 || c.equals(String.class)
140 || c.equals(Integer.class)
141 || c.equals(Boolean.class);
142 }
143
144 /**
145 * Returns true if the type is acceptable to be returned to the Skylark language.
146 */
147 public static boolean isSkylarkAcceptable(Class<?> c) {
148 return SkylarkValue.class.isAssignableFrom(c) // implements SkylarkValue
149 || c.equals(String.class) // basic values
150 || c.equals(Integer.class)
151 || c.equals(Boolean.class)
Irina Iancue9674fb2017-01-16 09:11:33 +0000152 // there is a registered Skylark ancestor class (useful e.g. when using AutoValue)
Jon Brandvein94d7f902017-01-18 23:32:53 +0000153 || SkylarkInterfaceUtils.getSkylarkModule(c) != null
Francois-Rene Rideau6c10eac2015-09-17 19:17:20 +0000154 || ImmutableMap.class.isAssignableFrom(c) // will be converted to SkylarkDict
155 || NestedSet.class.isAssignableFrom(c) // will be converted to SkylarkNestedSet
nharmataaac13242017-04-24 17:41:23 +0200156 || PathFragment.class.isAssignableFrom(c); // other known class
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100157 }
158
Francois-Rene Rideaub6091072015-02-25 15:48:30 +0000159 // TODO(bazel-team): move the following few type-related functions to SkylarkType
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100160 /**
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000161 * Return the Skylark-type of {@code c}
Francois-Rene Rideau3e9bab32015-03-06 13:41:00 +0000162 *
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000163 * <p>The result will be a type that Skylark understands and is either equal to {@code c}
164 * or is a supertype of it. For example, all instances of (all subclasses of) SkylarkList
165 * are considered to be SkylarkLists.
166 *
167 * <p>Skylark's type validation isn't equipped to deal with inheritance so we must tell it which
168 * of the superclasses or interfaces of {@code c} is the one that matters for type compatibility.
Francois-Rene Rideau3e9bab32015-03-06 13:41:00 +0000169 *
170 * @param c a class
171 * @return a super-class of c to be used in validation-time type inference.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100172 */
173 public static Class<?> getSkylarkType(Class<?> c) {
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000174 // TODO(bazel-team): replace these with SkylarkValue-s
175 if (String.class.equals(c)
176 || Boolean.class.equals(c)
177 || Integer.class.equals(c)
178 || Iterable.class.equals(c)
179 || Class.class.equals(c)) {
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000180 return c;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100181 }
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000182 // TODO(bazel-team): also unify all implementations of ClassObject,
183 // that we used to all print the same as "struct"?
Jon Brandvein65400722016-10-12 20:37:54 +0000184 Class<?> parent = SkylarkInterfaceUtils.getParentWithSkylarkModule(c);
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000185 if (parent != null) {
186 return parent;
187 }
188 Preconditions.checkArgument(SkylarkValue.class.isAssignableFrom(c),
189 "%s is not allowed as a Skylark value", c);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100190 return c;
191 }
192
193 /**
194 * Returns a pretty name for the datatype of object 'o' in the Build language.
195 */
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000196 public static String getDataTypeName(Object o) {
Francois-Rene Rideaub6091072015-02-25 15:48:30 +0000197 return getDataTypeName(o, false);
198 }
199
200 /**
201 * Returns a pretty name for the datatype of object {@code object} in Skylark
202 * or the BUILD language, with full details if the {@code full} boolean is true.
203 */
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000204 public static String getDataTypeName(Object object, boolean fullDetails) {
Francois-Rene Rideaub6091072015-02-25 15:48:30 +0000205 Preconditions.checkNotNull(object);
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000206 if (fullDetails) {
207 if (object instanceof SkylarkNestedSet) {
208 SkylarkNestedSet set = (SkylarkNestedSet) object;
Vladimir Moskvad200daf2016-12-23 16:35:37 +0000209 return "depset of " + set.getContentType() + "s";
Francois-Rene Rideaub6091072015-02-25 15:48:30 +0000210 }
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000211 if (object instanceof SelectorList) {
212 SelectorList list = (SelectorList) object;
213 return "select of " + getDataTypeNameFromClass(list.getType());
214 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100215 }
Francois-Rene Rideaua2c9ac62016-01-22 10:54:38 +0000216 return getDataTypeNameFromClass(object.getClass());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100217 }
218
219 /**
220 * Returns a pretty name for the datatype equivalent of class 'c' in the Build language.
221 */
222 public static String getDataTypeNameFromClass(Class<?> c) {
Florian Weikertee5e5e12015-08-11 16:47:31 +0000223 return getDataTypeNameFromClass(c, true);
224 }
225
226 /**
227 * Returns a pretty name for the datatype equivalent of class 'c' in the Build language.
228 * @param highlightNameSpaces Determines whether the result should also contain a special comment
229 * when the given class identifies a Skylark name space.
230 */
231 public static String getDataTypeNameFromClass(Class<?> c, boolean highlightNameSpaces) {
Jon Brandvein1171a7f2016-10-11 17:51:06 +0000232 SkylarkModule module = SkylarkInterfaceUtils.getSkylarkModule(c);
233 if (module != null) {
234 return module.name()
Francois-Rene Rideau4e994102015-09-17 22:41:28 +0000235 + ((module.namespace() && highlightNameSpaces) ? " (a language module)" : "");
236 } else if (c.equals(Object.class)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100237 return "unknown";
238 } else if (c.equals(String.class)) {
239 return "string";
240 } else if (c.equals(Integer.class)) {
241 return "int";
242 } else if (c.equals(Boolean.class)) {
243 return "bool";
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000244 } else if (List.class.isAssignableFrom(c)) { // This is a Java List that isn't a SkylarkList
245 return "List"; // This case shouldn't happen in normal code, but we keep it for debugging.
246 } else if (Map.class.isAssignableFrom(c)) { // This is a Java Map that isn't a SkylarkDict
247 return "Map"; // This case shouldn't happen in normal code, but we keep it for debugging.
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +0000248 } else if (BaseFunction.class.isAssignableFrom(c)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100249 return "function";
Greg Estren92e30d92015-04-09 21:58:28 +0000250 } else if (c.equals(SelectorValue.class)) {
251 return "select";
Vladimir Moskvad200daf2016-12-23 16:35:37 +0000252 } else if (NestedSet.class.isAssignableFrom(c)) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000253 // TODO(bazel-team): no one should be seeing naked NestedSet at all.
fwe3d2c75c2017-07-20 15:19:18 +0200254 return "depset";
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100255 } else {
256 if (c.getSimpleName().isEmpty()) {
257 return c.getName();
258 } else {
259 return c.getSimpleName();
260 }
261 }
262 }
263
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100264 public static Object checkNotNull(Expression expr, Object obj) throws EvalException {
265 if (obj == null) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000266 throw new EvalException(
267 expr.getLocation(),
268 "unexpected null value, please send a bug report. "
269 + "This was generated by expression '"
270 + expr
271 + "'");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100272 }
273 return obj;
274 }
275
276 /**
brandjon2b51f782017-07-25 21:05:04 +0200277 * Returns the truth value of an object, according to Python rules.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100278 * http://docs.python.org/2/library/stdtypes.html#truth-value-testing
279 */
280 public static boolean toBoolean(Object o) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000281 if (o == null || o == Runtime.NONE) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100282 return false;
283 } else if (o instanceof Boolean) {
284 return (Boolean) o;
285 } else if (o instanceof String) {
286 return !((String) o).isEmpty();
287 } else if (o instanceof Integer) {
288 return (Integer) o != 0;
289 } else if (o instanceof Collection<?>) {
290 return !((Collection<?>) o).isEmpty();
291 } else if (o instanceof Map<?, ?>) {
292 return !((Map<?, ?>) o).isEmpty();
293 } else if (o instanceof NestedSet<?>) {
294 return !((NestedSet<?>) o).isEmpty();
295 } else if (o instanceof SkylarkNestedSet) {
296 return !((SkylarkNestedSet) o).isEmpty();
297 } else if (o instanceof Iterable<?>) {
298 return !(Iterables.isEmpty((Iterable<?>) o));
299 } else {
300 return true;
301 }
302 }
303
laurentlbc9b6f4a2017-06-21 11:58:50 +0200304 public static Collection<?> toCollection(Object o, Location loc, @Nullable Environment env)
305 throws EvalException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100306 if (o instanceof Collection) {
Florian Weikert233a46e2015-12-16 12:38:38 +0000307 return (Collection<?>) o;
Laurent Le Brun741824b2015-03-20 15:10:19 +0000308 } else if (o instanceof SkylarkList) {
Laurent Le Brun3d776af2015-12-23 15:10:29 +0000309 return ((SkylarkList) o).getImmutableList();
Florian Weikert233a46e2015-12-16 12:38:38 +0000310 } else if (o instanceof Map) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100311 // For dictionaries we iterate through the keys only
Vladimir Moskva76e31d12016-12-05 16:28:37 +0000312 if (o instanceof SkylarkDict) {
313 // SkylarkDicts handle ordering themselves
314 SkylarkDict<?, ?> dict = (SkylarkDict) o;
315 List<Object> list = Lists.newArrayListWithCapacity(dict.size());
316 for (Map.Entry<?, ?> entries : dict.entrySet()) {
317 list.add(entries.getKey());
318 }
319 return ImmutableList.copyOf(list);
320 }
Laurent Le Brun196c1a72015-03-18 13:03:04 +0000321 // For determinism, we sort the keys.
Laurent Le Brunb54221c2015-04-16 16:14:37 +0000322 try {
Florian Weikert233a46e2015-12-16 12:38:38 +0000323 return SKYLARK_COMPARATOR.sortedCopy(((Map<?, ?>) o).keySet());
Laurent Le Brunb54221c2015-04-16 16:14:37 +0000324 } catch (ComparisonException e) {
325 throw new EvalException(loc, e);
326 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100327 } else if (o instanceof SkylarkNestedSet) {
laurentlbc9b6f4a2017-06-21 11:58:50 +0200328 return nestedSetToCollection((SkylarkNestedSet) o, loc, env);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100329 } else {
330 throw new EvalException(loc,
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000331 "type '" + getDataTypeName(o) + "' is not a collection");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100332 }
333 }
334
laurentlbc9b6f4a2017-06-21 11:58:50 +0200335 private static Collection<?> nestedSetToCollection(
336 SkylarkNestedSet set, Location loc, @Nullable Environment env) throws EvalException {
brandjon3c161912017-10-05 05:06:05 +0200337 if (env != null && env.getSemantics().incompatibleDepsetIsNotIterable()) {
laurentlbc9b6f4a2017-06-21 11:58:50 +0200338 throw new EvalException(
339 loc,
laurentlbc32efc82017-06-23 16:03:00 +0200340 "type 'depset' is not iterable. Use the `to_list()` method to get a list. Use "
341 + "--incompatible_depset_is_not_iterable=false to temporarily disable this check.");
laurentlbc9b6f4a2017-06-21 11:58:50 +0200342 }
343 return set.toCollection();
344 }
345
346 public static Iterable<?> toIterable(Object o, Location loc, @Nullable Environment env)
347 throws EvalException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100348 if (o instanceof String) {
349 // This is not as efficient as special casing String in for and dict and list comprehension
350 // statements. However this is a more unified way.
laurentlba752d8b2017-07-12 22:12:04 +0200351 return split((String) o, loc, env);
Jon Brandvein3cfeeec2017-01-20 04:23:37 +0000352 } else if (o instanceof SkylarkNestedSet) {
laurentlbc9b6f4a2017-06-21 11:58:50 +0200353 return nestedSetToCollection((SkylarkNestedSet) o, loc, env);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100354 } else if (o instanceof Iterable) {
Florian Weikert233a46e2015-12-16 12:38:38 +0000355 return (Iterable<?>) o;
356 } else if (o instanceof Map) {
laurentlbc9b6f4a2017-06-21 11:58:50 +0200357 return toCollection(o, loc, env);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100358 } else {
359 throw new EvalException(loc,
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000360 "type '" + getDataTypeName(o) + "' is not iterable");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100361 }
362 }
363
Jon Brandvein3cfeeec2017-01-20 04:23:37 +0000364 /**
365 * Given an {@link Iterable}, returns it as-is. Given a {@link SkylarkNestedSet}, returns its
366 * contents as an iterable. Throws {@link EvalException} for any other value.
367 *
368 * <p>This is a kludge for the change that made {@code SkylarkNestedSet} not implement {@code
369 * Iterable}. It is different from {@link #toIterable} in its behavior for strings and other types
370 * that are not strictly Java-iterable.
371 *
372 * @throws EvalException if {@code o} is not an iterable or set
373 * @deprecated avoid writing APIs that implicitly treat depsets as iterables. It encourages
laurentlbc9b6f4a2017-06-21 11:58:50 +0200374 * unnecessary flattening of depsets.
375 * <p>TODO(bazel-team): Remove this if/when implicit iteration over {@code SkylarkNestedSet}
376 * is no longer supported.
Jon Brandvein3cfeeec2017-01-20 04:23:37 +0000377 */
378 @Deprecated
laurentlbc9b6f4a2017-06-21 11:58:50 +0200379 public static Iterable<?> toIterableStrict(Object o, Location loc, @Nullable Environment env)
380 throws EvalException {
Jon Brandvein3cfeeec2017-01-20 04:23:37 +0000381 if (o instanceof Iterable) {
382 return (Iterable<?>) o;
383 } else if (o instanceof SkylarkNestedSet) {
laurentlbc9b6f4a2017-06-21 11:58:50 +0200384 return nestedSetToCollection((SkylarkNestedSet) o, loc, env);
Jon Brandvein3cfeeec2017-01-20 04:23:37 +0000385 } else {
386 throw new EvalException(loc,
387 "expected Iterable or depset, but got '" + getDataTypeName(o) + "' (strings and maps "
388 + "are not allowed here)");
389 }
390 }
391
Jon Brandvein65e3ae62016-09-27 19:57:40 +0000392 public static void lock(Object object, Location loc) {
393 if (object instanceof SkylarkMutable) {
394 ((SkylarkMutable) object).lock(loc);
395 }
396 }
397
398 public static void unlock(Object object, Location loc) {
399 if (object instanceof SkylarkMutable) {
400 ((SkylarkMutable) object).unlock(loc);
401 }
402 }
403
laurentlba752d8b2017-07-12 22:12:04 +0200404 private static ImmutableList<String> split(String value, Location loc, @Nullable Environment env)
405 throws EvalException {
brandjon3c161912017-10-05 05:06:05 +0200406 if (env != null && env.getSemantics().incompatibleStringIsNotIterable()) {
laurentlba752d8b2017-07-12 22:12:04 +0200407 throw new EvalException(
408 loc,
409 "type 'string' is not iterable. You may still use `len` and string indexing. Use "
410 + "--incompatible_string_is_not_iterable=false to temporarily disable this check.");
411 }
412
Florian Weikert233a46e2015-12-16 12:38:38 +0000413 ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
414 for (char c : value.toCharArray()) {
415 builder.add(String.valueOf(c));
416 }
417 return builder.build();
418 }
419
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100420 /**
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000421 * @return the size of the Skylark object or -1 in case the object doesn't have a size.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100422 */
423 public static int size(Object arg) {
424 if (arg instanceof String) {
425 return ((String) arg).length();
426 } else if (arg instanceof Map) {
427 return ((Map<?, ?>) arg).size();
428 } else if (arg instanceof SkylarkList) {
Jon Brandvein3cfeeec2017-01-20 04:23:37 +0000429 return ((SkylarkList<?>) arg).size();
430 } else if (arg instanceof SkylarkNestedSet) {
431 // TODO(bazel-team): Add a deprecation warning: don't implicitly flatten depsets.
432 return ((SkylarkNestedSet) arg).toCollection().size();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100433 } else if (arg instanceof Iterable) {
434 // Iterables.size() checks if arg is a Collection so it's efficient in that sense.
435 return Iterables.size((Iterable<?>) arg);
436 }
437 return -1;
438 }
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000439
Jon Brandveinfab84872016-11-11 16:27:01 +0000440 // The following functions for indexing and slicing match the behavior of Python.
441
442 /**
443 * Resolves a positive or negative index to an index in the range [0, length), or throws
444 * EvalException if it is out-of-range. If the index is negative, it counts backward from
445 * length.
446 */
447 public static int getSequenceIndex(int index, int length, Location loc)
448 throws EvalException {
449 int actualIndex = index;
450 if (actualIndex < 0) {
451 actualIndex += length;
452 }
453 if (actualIndex < 0 || actualIndex >= length) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000454 throw new EvalException(
455 loc,
456 "index out of range (index is " + index + ", but sequence has " + length + " elements)");
Jon Brandveinfab84872016-11-11 16:27:01 +0000457 }
458 return actualIndex;
459 }
460
461 /**
462 * Performs index resolution after verifying that the given object has index type.
463 */
464 public static int getSequenceIndex(Object index, int length, Location loc)
465 throws EvalException {
466 if (!(index instanceof Integer)) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000467 throw new EvalException(
468 loc, "indices must be integers, not " + EvalUtils.getDataTypeName(index));
Jon Brandveinfab84872016-11-11 16:27:01 +0000469 }
470 return getSequenceIndex(((Integer) index).intValue(), length, loc);
471 }
472
473 /**
474 * Resolves a positive or negative index to an integer that can denote the left or right boundary
475 * of a slice. If reverse is false, the slice has positive stride (i.e., its elements are in their
476 * normal order) and the result is guaranteed to be in range [0, length + 1). If reverse is true,
477 * the slice has negative stride and the result is in range [-1, length). In either case, if the
478 * index is negative, it counts backward from length. Note that an input index of -1 represents
479 * the last element's position, while an output integer of -1 represents the imaginary position
480 * to the left of the first element.
481 */
482 public static int clampRangeEndpoint(int index, int length, boolean reverse) {
483 if (index < 0) {
484 index += length;
485 }
486 if (!reverse) {
487 return Math.max(Math.min(index, length), 0);
488 } else {
489 return Math.max(Math.min(index, length - 1), -1);
490 }
491 }
492
493 /**
494 * Resolves a positive or negative index to an integer that can denote the boundary for a
495 * slice with positive stride.
496 */
497 public static int clampRangeEndpoint(int index, int length) {
498 return clampRangeEndpoint(index, length, false);
499 }
500
501 /**
502 * Calculates the indices of the elements that should be included in the slice [start:end:step]
503 * of a sequence with the given length. Each of start, end, and step must be supplied, and step
504 * may not be 0.
505 */
506 public static List<Integer> getSliceIndices(int start, int end, int step, int length) {
507 if (step == 0) {
508 throw new IllegalArgumentException("Slice step cannot be zero");
509 }
510 start = clampRangeEndpoint(start, length, step < 0);
511 end = clampRangeEndpoint(end, length, step < 0);
512 ImmutableList.Builder<Integer> indices = ImmutableList.builder();
513 for (int current = start; step > 0 ? current < end : current > end; current += step) {
514 indices.add(current);
515 }
516 return indices.build();
517 }
518
519 /**
520 * Calculates the indices of the elements in a slice, after validating the arguments and replacing
521 * Runtime.NONE with default values. Throws an EvalException if a bad argument is given.
522 */
523 public static List<Integer> getSliceIndices(
524 Object startObj, Object endObj, Object stepObj, int length, Location loc)
525 throws EvalException {
526 int start;
527 int end;
528 int step;
529
530 if (stepObj == Runtime.NONE) {
531 // This case is excluded by the parser, but let's handle it for completeness.
532 step = 1;
533 } else if (stepObj instanceof Integer) {
534 step = ((Integer) stepObj).intValue();
535 } else {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000536 throw new EvalException(
537 loc, String.format("slice step must be an integer, not '%s'", stepObj));
Jon Brandveinfab84872016-11-11 16:27:01 +0000538 }
539 if (step == 0) {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000540 throw new EvalException(loc, "slice step cannot be zero");
Jon Brandveinfab84872016-11-11 16:27:01 +0000541 }
542
543 if (startObj == Runtime.NONE) {
544 start = (step > 0) ? 0 : length - 1;
545 } else if (startObj instanceof Integer) {
546 start = ((Integer) startObj).intValue();
547 } else {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000548 throw new EvalException(
549 loc, String.format("slice start must be an integer, not '%s'", startObj));
Jon Brandveinfab84872016-11-11 16:27:01 +0000550 }
551 if (endObj == Runtime.NONE) {
552 // If step is negative, can't use -1 for end since that would be converted
553 // to the rightmost element's position.
554 end = (step > 0) ? length : -length - 1;
555 } else if (endObj instanceof Integer) {
556 end = ((Integer) endObj).intValue();
557 } else {
Laurent Le Brunc31f3512016-12-29 21:41:33 +0000558 throw new EvalException(loc, String.format("slice end must be an integer, not '%s'", endObj));
Jon Brandveinfab84872016-11-11 16:27:01 +0000559 }
560
561 return getSliceIndices(start, end, step, length);
562 }
563
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000564 /** @return true if x is Java null or Skylark None */
565 public static boolean isNullOrNone(Object x) {
Francois-Rene Rideau0f7ba342015-08-31 16:16:21 +0000566 return x == null || x == Runtime.NONE;
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000567 }
568
569 /**
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000570 * Build a SkylarkDict of kwarg arguments from a list, removing null-s or None-s.
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000571 *
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000572 * @param env the Environment in which this map can be mutated.
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000573 * @param init a series of key, value pairs (as consecutive arguments)
Francois-Rene Rideau328f6152016-01-06 19:50:27 +0000574 * as in {@code optionMap(k1, v1, k2, v2, k3, v3)}
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000575 * where each key is a String, each value is an arbitrary Objet.
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000576 * @return a {@code Map<String, Object>} that has all the specified entries,
577 * where key, value pairs appearing earlier have precedence,
578 * i.e. {@code k1, v1} may override {@code k3, v3}.
579 *
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000580 * Ignore any entry where the value is null or None.
581 * Keys cannot be null.
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000582 */
583 @SuppressWarnings("unchecked")
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000584 public static <K, V> SkylarkDict<K, V> optionMap(Environment env, Object... init) {
585 ImmutableMap.Builder<K, V> b = new ImmutableMap.Builder<>();
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000586 Preconditions.checkState(init.length % 2 == 0);
587 for (int i = init.length - 2; i >= 0; i -= 2) {
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000588 K key = (K) Preconditions.checkNotNull(init[i]);
589 V value = (V) init[i + 1];
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000590 if (!isNullOrNone(value)) {
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000591 b.put(key, value);
592 }
593 }
brandjonc06e7462017-07-11 20:54:58 +0200594 return SkylarkDict.copyOf(env, b.build());
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000595 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100596}