blob: 52b7e1f5e3cff3429e0deea7c09d164c17c22bdd [file] [log] [blame]
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001// Copyright 2014 Google Inc. 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.
14package com.google.devtools.build.lib.syntax;
15
16import com.google.common.annotations.VisibleForTesting;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010017import com.google.common.base.Preconditions;
18import com.google.common.collect.ImmutableList;
19import com.google.common.collect.ImmutableMap;
20import com.google.common.collect.ImmutableSet;
21import com.google.common.collect.Iterables;
Laurent Le Brun196c1a72015-03-18 13:03:04 +000022import com.google.common.collect.Ordering;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010023import com.google.devtools.build.lib.collect.nestedset.NestedSet;
24import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
25import com.google.devtools.build.lib.events.Location;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010026
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010027import java.util.Collection;
Laurent Le Brunb54221c2015-04-16 16:14:37 +000028import java.util.Comparator;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010029import java.util.List;
30import java.util.Map;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010031import java.util.Set;
32
33/**
34 * Utilities used by the evaluator.
35 */
36public abstract class EvalUtils {
37
Laurent Le Brunb54221c2015-04-16 16:14:37 +000038 /**
39 * The exception that SKYLARK_COMPARATOR might throw. This is an unchecked exception
40 * because Comparator doesn't let us declare exceptions. It should normally be caught
41 * and wrapped in an EvalException.
42 */
43 public static class ComparisonException extends RuntimeException {
44 public ComparisonException(String msg) {
45 super(msg);
46 }
47 }
48
49 /**
50 * Compare two Skylark objects.
51 *
52 * <p> It may throw an unchecked exception ComparisonException that should be wrapped in
53 * an EvalException.
54 */
55 public static final Comparator<Object> SKYLARK_COMPARATOR = new Comparator<Object>() {
56 private int compareLists(SkylarkList o1, SkylarkList o2) {
57 for (int i = 0; i < Math.min(o1.size(), o2.size()); i++) {
58 int cmp = compare(o1.get(i), o2.get(i));
59 if (cmp != 0) {
60 return cmp;
61 }
62 }
63 return Integer.compare(o1.size(), o2.size());
64 }
65
66 @Override
67 @SuppressWarnings("unchecked")
68 public int compare(Object o1, Object o2) {
69 Location loc = null;
70 try {
71 o1 = SkylarkType.convertToSkylark(o1, loc);
72 o2 = SkylarkType.convertToSkylark(o2, loc);
73 } catch (EvalException e) {
74 throw new ComparisonException(e.getMessage());
75 }
76
77 if (o1 instanceof SkylarkList && o2 instanceof SkylarkList) {
78 return compareLists((SkylarkList) o1, (SkylarkList) o2);
79 }
80 try {
81 return ((Comparable<Object>) o1).compareTo(o2);
82 } catch (ClassCastException e) {
Florian Weikertf31b9472015-08-04 16:36:58 +000083 try {
84 // Different types -> let the class names decide
85 return o1.getClass().getName().compareTo(o2.getClass().getName());
86 } catch (NullPointerException ex) {
87 throw new ComparisonException(
88 "Cannot compare " + getDataTypeName(o1) + " with " + EvalUtils.getDataTypeName(o2));
89 }
Laurent Le Brunb54221c2015-04-16 16:14:37 +000090 }
91 }
92 };
93
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010094 // TODO(bazel-team): Yet an other hack committed in the name of Skylark. One problem is that the
95 // syntax package cannot depend on actions so we have to have this until Actions are immutable.
96 // The other is that BuildConfigurations are technically not immutable but they cannot be modified
97 // from Skylark.
98 private static final ImmutableSet<Class<?>> quasiImmutableClasses;
99 static {
100 try {
101 ImmutableSet.Builder<Class<?>> builder = ImmutableSet.builder();
102 builder.add(Class.forName("com.google.devtools.build.lib.actions.Action"));
103 builder.add(Class.forName("com.google.devtools.build.lib.analysis.config.BuildConfiguration"));
104 builder.add(Class.forName("com.google.devtools.build.lib.actions.Root"));
105 quasiImmutableClasses = builder.build();
106 } catch (ClassNotFoundException e) {
107 throw new RuntimeException(e);
108 }
109 }
110
111 private EvalUtils() {
112 }
113
114 /**
115 * @return true if the specified sequence is a tuple; false if it's a modifiable list.
116 */
117 public static boolean isTuple(List<?> l) {
118 return isTuple(l.getClass());
119 }
120
121 public static boolean isTuple(Class<?> c) {
122 Preconditions.checkState(List.class.isAssignableFrom(c));
Ulf Adams07dba942015-03-05 14:47:37 +0000123 return ImmutableList.class.isAssignableFrom(c);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100124 }
125
126 /**
127 * @return true if the specified value is immutable (suitable for use as a
128 * dictionary key) according to the rules of the Build language.
129 */
130 public static boolean isImmutable(Object o) {
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +0000131 if (o instanceof Map<?, ?> || o instanceof BaseFunction
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100132 || o instanceof FilesetEntry || o instanceof GlobList<?>) {
133 return false;
134 } else if (o instanceof List<?>) {
135 return isTuple((List<?>) o); // tuples are immutable, lists are not.
136 } else {
137 return true; // string/int
138 }
139 }
140
141 /**
142 * Returns true if the type is immutable in the skylark language.
143 */
144 public static boolean isSkylarkImmutable(Class<?> c) {
145 if (c.isAnnotationPresent(Immutable.class)) {
146 return true;
147 } else if (c.equals(String.class) || c.equals(Integer.class) || c.equals(Boolean.class)
148 || SkylarkList.class.isAssignableFrom(c) || ImmutableMap.class.isAssignableFrom(c)
149 || NestedSet.class.isAssignableFrom(c)) {
150 return true;
151 } else {
152 for (Class<?> classObject : quasiImmutableClasses) {
153 if (classObject.isAssignableFrom(c)) {
154 return true;
155 }
156 }
157 }
158 return false;
159 }
160
161 /**
162 * Returns a transitive superclass or interface implemented by c which is annotated
163 * with SkylarkModule. Returns null if no such class or interface exists.
164 */
165 @VisibleForTesting
166 static Class<?> getParentWithSkylarkModule(Class<?> c) {
167 if (c == null) {
168 return null;
169 }
170 if (c.isAnnotationPresent(SkylarkModule.class)) {
171 return c;
172 }
173 Class<?> parent = getParentWithSkylarkModule(c.getSuperclass());
174 if (parent != null) {
175 return parent;
176 }
177 for (Class<?> ifparent : c.getInterfaces()) {
178 ifparent = getParentWithSkylarkModule(ifparent);
179 if (ifparent != null) {
180 return ifparent;
181 }
182 }
183 return null;
184 }
185
Francois-Rene Rideaub6091072015-02-25 15:48:30 +0000186 // TODO(bazel-team): move the following few type-related functions to SkylarkType
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100187 /**
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000188 * Return the Skylark-type of {@code c}
Francois-Rene Rideau3e9bab32015-03-06 13:41:00 +0000189 *
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000190 * <p>The result will be a type that Skylark understands and is either equal to {@code c}
191 * or is a supertype of it. For example, all instances of (all subclasses of) SkylarkList
192 * are considered to be SkylarkLists.
193 *
194 * <p>Skylark's type validation isn't equipped to deal with inheritance so we must tell it which
195 * of the superclasses or interfaces of {@code c} is the one that matters for type compatibility.
Francois-Rene Rideau3e9bab32015-03-06 13:41:00 +0000196 *
197 * @param c a class
198 * @return a super-class of c to be used in validation-time type inference.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100199 */
200 public static Class<?> getSkylarkType(Class<?> c) {
201 if (ImmutableList.class.isAssignableFrom(c)) {
202 return ImmutableList.class;
203 } else if (List.class.isAssignableFrom(c)) {
204 return List.class;
205 } else if (SkylarkList.class.isAssignableFrom(c)) {
206 return SkylarkList.class;
207 } else if (Map.class.isAssignableFrom(c)) {
208 return Map.class;
209 } else if (NestedSet.class.isAssignableFrom(c)) {
210 // This could be removed probably
211 return NestedSet.class;
212 } else if (Set.class.isAssignableFrom(c)) {
213 return Set.class;
214 } else {
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000215 // TODO(bazel-team): also unify all implementations of ClassObject,
216 // that we used to all print the same as "struct"?
Francois-Rene Rideau3e9bab32015-03-06 13:41:00 +0000217 //
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100218 // Check if one of the superclasses or implemented interfaces has the SkylarkModule
219 // annotation. If yes return that class.
220 Class<?> parent = getParentWithSkylarkModule(c);
221 if (parent != null) {
222 return parent;
223 }
224 }
225 return c;
226 }
227
228 /**
229 * Returns a pretty name for the datatype of object 'o' in the Build language.
230 */
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000231 public static String getDataTypeName(Object o) {
Francois-Rene Rideaub6091072015-02-25 15:48:30 +0000232 return getDataTypeName(o, false);
233 }
234
235 /**
236 * Returns a pretty name for the datatype of object {@code object} in Skylark
237 * or the BUILD language, with full details if the {@code full} boolean is true.
238 */
239 public static String getDataTypeName(Object object, boolean full) {
240 Preconditions.checkNotNull(object);
241 if (object instanceof SkylarkList) {
242 SkylarkList list = (SkylarkList) object;
243 if (list.isTuple()) {
244 return "tuple";
245 } else {
Francois-Rene Rideau22513332015-02-27 15:20:29 +0000246 return "list" + (full ? " of " + list.getContentType() + "s" : "");
Francois-Rene Rideaub6091072015-02-25 15:48:30 +0000247 }
248 } else if (object instanceof SkylarkNestedSet) {
249 SkylarkNestedSet set = (SkylarkNestedSet) object;
Francois-Rene Rideau22513332015-02-27 15:20:29 +0000250 return "set" + (full ? " of " + set.getContentType() + "s" : "");
Francois-Rene Rideaub6091072015-02-25 15:48:30 +0000251 } else {
252 return getDataTypeNameFromClass(object.getClass());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100253 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100254 }
255
256 /**
257 * Returns a pretty name for the datatype equivalent of class 'c' in the Build language.
258 */
259 public static String getDataTypeNameFromClass(Class<?> c) {
260 if (c.equals(Object.class)) {
261 return "unknown";
262 } else if (c.equals(String.class)) {
263 return "string";
264 } else if (c.equals(Integer.class)) {
265 return "int";
266 } else if (c.equals(Boolean.class)) {
267 return "bool";
268 } else if (c.equals(Void.TYPE) || c.equals(Environment.NoneType.class)) {
Laurent Le Brunc6281bd2015-05-08 11:59:42 +0000269 return "NoneType";
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100270 } else if (List.class.isAssignableFrom(c)) {
Francois-Rene Rideauf249d762015-03-02 08:14:46 +0000271 // NB: the capital here is a subtle way to distinguish java Tuple and java List
272 // from native SkylarkList tuple and list.
273 // TODO(bazel-team): refactor SkylarkList and use it everywhere.
274 return isTuple(c) ? "Tuple" : "List";
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100275 } else if (GlobList.class.isAssignableFrom(c)) {
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000276 return "glob list";
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100277 } else if (Map.class.isAssignableFrom(c)) {
278 return "dict";
Francois-Rene Rideau95b0d0c2015-04-22 16:52:13 +0000279 } else if (BaseFunction.class.isAssignableFrom(c)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100280 return "function";
281 } else if (c.equals(FilesetEntry.class)) {
282 return "FilesetEntry";
Greg Estren92e30d92015-04-09 21:58:28 +0000283 } else if (c.equals(SelectorValue.class)) {
284 return "select";
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100285 } else if (NestedSet.class.isAssignableFrom(c) || SkylarkNestedSet.class.isAssignableFrom(c)) {
286 return "set";
Francois-Rene Rideau3e9bab32015-03-06 13:41:00 +0000287 } else if (ClassObject.SkylarkClassObject.class.isAssignableFrom(c)) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100288 return "struct";
289 } else if (SkylarkList.class.isAssignableFrom(c)) {
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000290 // TODO(bazel-team): Refactor the class hierarchy so we can distinguish list and tuple types.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100291 return "list";
292 } else if (c.isAnnotationPresent(SkylarkModule.class)) {
293 SkylarkModule module = c.getAnnotation(SkylarkModule.class);
294 return c.getAnnotation(SkylarkModule.class).name()
295 + (module.namespace() ? " (a language module)" : "");
296 } else {
297 if (c.getSimpleName().isEmpty()) {
298 return c.getName();
299 } else {
300 return c.getSimpleName();
301 }
302 }
303 }
304
305 /**
306 * Returns a sequence of the appropriate list/tuple datatype for 'seq', based on 'isTuple'.
307 */
308 public static List<?> makeSequence(List<?> seq, boolean isTuple) {
309 return isTuple ? ImmutableList.copyOf(seq) : seq;
310 }
311
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100312 public static Object checkNotNull(Expression expr, Object obj) throws EvalException {
313 if (obj == null) {
314 throw new EvalException(expr.getLocation(),
315 "Unexpected null value, please send a bug report. "
316 + "This was generated by '" + expr + "'");
317 }
318 return obj;
319 }
320
321 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100322 * @return the truth value of an object, according to Python rules.
323 * http://docs.python.org/2/library/stdtypes.html#truth-value-testing
324 */
325 public static boolean toBoolean(Object o) {
326 if (o == null || o == Environment.NONE) {
327 return false;
328 } else if (o instanceof Boolean) {
329 return (Boolean) o;
330 } else if (o instanceof String) {
331 return !((String) o).isEmpty();
332 } else if (o instanceof Integer) {
333 return (Integer) o != 0;
334 } else if (o instanceof Collection<?>) {
335 return !((Collection<?>) o).isEmpty();
336 } else if (o instanceof Map<?, ?>) {
337 return !((Map<?, ?>) o).isEmpty();
338 } else if (o instanceof NestedSet<?>) {
339 return !((NestedSet<?>) o).isEmpty();
340 } else if (o instanceof SkylarkNestedSet) {
341 return !((SkylarkNestedSet) o).isEmpty();
342 } else if (o instanceof Iterable<?>) {
343 return !(Iterables.isEmpty((Iterable<?>) o));
344 } else {
345 return true;
346 }
347 }
348
349 @SuppressWarnings("unchecked")
Laurent Le Brun196c1a72015-03-18 13:03:04 +0000350 public static Collection<?> toCollection(Object o, Location loc) throws EvalException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100351 if (o instanceof Collection) {
352 return (Collection<Object>) o;
Laurent Le Brun741824b2015-03-20 15:10:19 +0000353 } else if (o instanceof SkylarkList) {
354 return ((SkylarkList) o).toList();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100355 } else if (o instanceof Map<?, ?>) {
Laurent Le Brun196c1a72015-03-18 13:03:04 +0000356 Map<Comparable<?>, Object> dict = (Map<Comparable<?>, Object>) o;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100357 // For dictionaries we iterate through the keys only
Laurent Le Brun196c1a72015-03-18 13:03:04 +0000358 // For determinism, we sort the keys.
Laurent Le Brunb54221c2015-04-16 16:14:37 +0000359 try {
360 return Ordering.from(SKYLARK_COMPARATOR).sortedCopy(dict.keySet());
361 } catch (ComparisonException e) {
362 throw new EvalException(loc, e);
363 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100364 } else if (o instanceof SkylarkNestedSet) {
365 return ((SkylarkNestedSet) o).toCollection();
366 } else {
367 throw new EvalException(loc,
Francois-Rene Rideaucbebd632015-02-11 16:56:37 +0000368 "type '" + getDataTypeName(o) + "' is not a collection");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100369 }
370 }
371
372 @SuppressWarnings("unchecked")
Laurent Le Brun196c1a72015-03-18 13:03:04 +0000373 public static Iterable<?> toIterable(Object o, Location loc) throws EvalException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100374 if (o instanceof String) {
375 // This is not as efficient as special casing String in for and dict and list comprehension
376 // statements. However this is a more unified way.
377 // The regex matches every character in the string until the end of the string,
378 // so "abc" will be split into ["a", "b", "c"].
379 return ImmutableList.<Object>copyOf(((String) o).split("(?!^)"));
380 } else if (o instanceof Iterable) {
381 return (Iterable<Object>) o;
382 } else if (o instanceof Map<?, ?>) {
Laurent Le Brun196c1a72015-03-18 13:03:04 +0000383 return toCollection(o, loc);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100384 } else {
385 throw new EvalException(loc,
Francois-Rene Rideau5f3e30c2015-04-10 19:08:39 +0000386 "type '" + getDataTypeName(o) + "' is not iterable");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100387 }
388 }
389
390 /**
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000391 * @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 +0100392 */
393 public static int size(Object arg) {
394 if (arg instanceof String) {
395 return ((String) arg).length();
396 } else if (arg instanceof Map) {
397 return ((Map<?, ?>) arg).size();
398 } else if (arg instanceof SkylarkList) {
399 return ((SkylarkList) arg).size();
400 } else if (arg instanceof Iterable) {
401 // Iterables.size() checks if arg is a Collection so it's efficient in that sense.
402 return Iterables.size((Iterable<?>) arg);
403 }
404 return -1;
405 }
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000406
407 /** @return true if x is Java null or Skylark None */
408 public static boolean isNullOrNone(Object x) {
409 return x == null || x == Environment.NONE;
410 }
411
412 /**
413 * Build a map of kwarg arguments from a list, removing null-s or None-s.
414 *
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000415 * @param init a series of key, value pairs (as consecutive arguments)
416 * as in {@code optionMap(k1, v1, k2, v2, k3, v3, map)}
417 * where each key is a String, each value is an arbitrary Objet.
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000418 * @return a {@code Map<String, Object>} that has all the specified entries,
419 * where key, value pairs appearing earlier have precedence,
420 * i.e. {@code k1, v1} may override {@code k3, v3}.
421 *
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000422 * Ignore any entry where the value is null or None.
423 * Keys cannot be null.
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000424 */
425 @SuppressWarnings("unchecked")
426 public static ImmutableMap<String, Object> optionMap(Object... init) {
427 ImmutableMap.Builder<String, Object> b = new ImmutableMap.Builder<>();
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000428 Preconditions.checkState(init.length % 2 == 0);
429 for (int i = init.length - 2; i >= 0; i -= 2) {
430 String key = (String) Preconditions.checkNotNull(init[i]);
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000431 Object value = init[i + 1];
Laurent Le Brun59fa1c12015-06-03 14:56:03 +0000432 if (!isNullOrNone(value)) {
Francois-Rene Rideaub6ab6f22015-03-10 16:05:12 +0000433 b.put(key, value);
434 }
435 }
436 return b.build();
437 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100438}