Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 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 | package com.google.devtools.build.lib.syntax; |
| 15 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 16 | import com.google.common.collect.ImmutableList; |
| 17 | import com.google.common.collect.ImmutableMap; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 18 | import com.google.common.collect.Iterables; |
Vladimir Moskva | 76e31d1 | 2016-12-05 16:28:37 +0000 | [diff] [blame] | 19 | import com.google.common.collect.Lists; |
Laurent Le Brun | 196c1a7 | 2015-03-18 13:03:04 +0000 | [diff] [blame] | 20 | import com.google.common.collect.Ordering; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 21 | import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| 22 | import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; |
| 23 | import com.google.devtools.build.lib.events.Location; |
Jon Brandvein | 1171a7f | 2016-10-11 17:51:06 +0000 | [diff] [blame] | 24 | import com.google.devtools.build.lib.skylarkinterface.SkylarkInterfaceUtils; |
John Field | 585d1a0 | 2015-12-16 16:03:52 +0000 | [diff] [blame] | 25 | import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; |
| 26 | import com.google.devtools.build.lib.skylarkinterface.SkylarkValue; |
Mark Schaller | 6df8179 | 2015-12-10 18:47:47 +0000 | [diff] [blame] | 27 | import com.google.devtools.build.lib.util.Preconditions; |
Francois-Rene Rideau | 6c10eac | 2015-09-17 19:17:20 +0000 | [diff] [blame] | 28 | import com.google.devtools.build.lib.vfs.PathFragment; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 29 | import java.util.Collection; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 30 | import java.util.List; |
| 31 | import java.util.Map; |
laurentlb | c9b6f4a | 2017-06-21 11:58:50 +0200 | [diff] [blame] | 32 | import javax.annotation.Nullable; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 33 | |
| 34 | /** |
| 35 | * Utilities used by the evaluator. |
| 36 | */ |
Francois-Rene Rideau | 6c10eac | 2015-09-17 19:17:20 +0000 | [diff] [blame] | 37 | public final class EvalUtils { |
| 38 | |
| 39 | private EvalUtils() {} |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 40 | |
Laurent Le Brun | b54221c | 2015-04-16 16:14:37 +0000 | [diff] [blame] | 41 | /** |
| 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 Moskva | d200daf | 2016-12-23 16:35:37 +0000 | [diff] [blame] | 55 | * <p>It may throw an unchecked exception ComparisonException that should be wrapped in an |
| 56 | * EvalException. |
Laurent Le Brun | b54221c | 2015-04-16 16:14:37 +0000 | [diff] [blame] | 57 | */ |
Vladimir Moskva | d200daf | 2016-12-23 16:35:37 +0000 | [diff] [blame] | 58 | 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 Brun | b54221c | 2015-04-16 16:14:37 +0000 | [diff] [blame] | 68 | } |
Laurent Le Brun | b54221c | 2015-04-16 16:14:37 +0000 | [diff] [blame] | 69 | |
Vladimir Moskva | d200daf | 2016-12-23 16:35:37 +0000 | [diff] [blame] | 70 | @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 Brun | b54221c | 2015-04-16 16:14:37 +0000 | [diff] [blame] | 75 | |
Vladimir Moskva | d200daf | 2016-12-23 16:35:37 +0000 | [diff] [blame] | 76 | if (o1 instanceof SkylarkList |
| 77 | && o2 instanceof SkylarkList |
| 78 | && ((SkylarkList) o1).isTuple() == ((SkylarkList) o2).isTuple()) { |
| 79 | return compareLists((SkylarkList) o1, (SkylarkList) o2); |
| 80 | } |
Vladimir Moskva | 7f0cd62 | 2017-02-16 13:48:37 +0000 | [diff] [blame] | 81 | 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 Moskva | d200daf | 2016-12-23 16:35:37 +0000 | [diff] [blame] | 92 | try { |
| 93 | return ((Comparable<Object>) o1).compareTo(o2); |
| 94 | } catch (ClassCastException e) { |
Vladimir Moskva | 7f0cd62 | 2017-02-16 13:48:37 +0000 | [diff] [blame] | 95 | throw new ComparisonException( |
| 96 | "Cannot compare " + getDataTypeName(o1) + " with " + getDataTypeName(o2)); |
| 97 | } |
| 98 | } |
| 99 | }; |
| 100 | |
| 101 | /** |
Francois-Rene Rideau | 6c10eac | 2015-09-17 19:17:20 +0000 | [diff] [blame] | 102 | * Checks that an Object is a valid key for a Skylark dict. |
| 103 | * @param o an Object to validate |
Lukacs Berki | ffa73ad | 2015-09-18 11:40:12 +0000 | [diff] [blame] | 104 | * @throws EvalException if o is not a valid key |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 105 | */ |
Klaas Boesche | 53de62a | 2015-11-06 15:12:10 +0000 | [diff] [blame] | 106 | public static void checkValidDictKey(Object o) throws EvalException { |
Francois-Rene Rideau | 6c10eac | 2015-09-17 19:17:20 +0000 | [diff] [blame] | 107 | // 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 Rideau | a2c9ac6 | 2016-01-22 10:54:38 +0000 | [diff] [blame] | 120 | // 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 Rideau | 6c10eac | 2015-09-17 19:17:20 +0000 | [diff] [blame] | 122 | public static boolean isImmutable(Object o) { |
| 123 | if (o instanceof SkylarkValue) { |
| 124 | return ((SkylarkValue) o).isImmutable(); |
| 125 | } |
Francois-Rene Rideau | a2c9ac6 | 2016-01-22 10:54:38 +0000 | [diff] [blame] | 126 | return isImmutable(o.getClass()); |
Francois-Rene Rideau | 6c10eac | 2015-09-17 19:17:20 +0000 | [diff] [blame] | 127 | } |
| 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 Iancu | e9674fb | 2017-01-16 09:11:33 +0000 | [diff] [blame] | 152 | // there is a registered Skylark ancestor class (useful e.g. when using AutoValue) |
Jon Brandvein | 94d7f90 | 2017-01-18 23:32:53 +0000 | [diff] [blame] | 153 | || SkylarkInterfaceUtils.getSkylarkModule(c) != null |
Francois-Rene Rideau | 6c10eac | 2015-09-17 19:17:20 +0000 | [diff] [blame] | 154 | || ImmutableMap.class.isAssignableFrom(c) // will be converted to SkylarkDict |
| 155 | || NestedSet.class.isAssignableFrom(c) // will be converted to SkylarkNestedSet |
nharmata | aac1324 | 2017-04-24 17:41:23 +0200 | [diff] [blame] | 156 | || PathFragment.class.isAssignableFrom(c); // other known class |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 157 | } |
| 158 | |
Francois-Rene Rideau | b609107 | 2015-02-25 15:48:30 +0000 | [diff] [blame] | 159 | // TODO(bazel-team): move the following few type-related functions to SkylarkType |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 160 | /** |
Francois-Rene Rideau | b6ab6f2 | 2015-03-10 16:05:12 +0000 | [diff] [blame] | 161 | * Return the Skylark-type of {@code c} |
Francois-Rene Rideau | 3e9bab3 | 2015-03-06 13:41:00 +0000 | [diff] [blame] | 162 | * |
Francois-Rene Rideau | b6ab6f2 | 2015-03-10 16:05:12 +0000 | [diff] [blame] | 163 | * <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 Rideau | 3e9bab3 | 2015-03-06 13:41:00 +0000 | [diff] [blame] | 169 | * |
| 170 | * @param c a class |
| 171 | * @return a super-class of c to be used in validation-time type inference. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 172 | */ |
| 173 | public static Class<?> getSkylarkType(Class<?> c) { |
Francois-Rene Rideau | ab049e0 | 2016-02-17 16:13:46 +0000 | [diff] [blame] | 174 | // 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 Rideau | a2c9ac6 | 2016-01-22 10:54:38 +0000 | [diff] [blame] | 180 | return c; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 181 | } |
Francois-Rene Rideau | ab049e0 | 2016-02-17 16:13:46 +0000 | [diff] [blame] | 182 | // TODO(bazel-team): also unify all implementations of ClassObject, |
| 183 | // that we used to all print the same as "struct"? |
Jon Brandvein | 6540072 | 2016-10-12 20:37:54 +0000 | [diff] [blame] | 184 | Class<?> parent = SkylarkInterfaceUtils.getParentWithSkylarkModule(c); |
Francois-Rene Rideau | ab049e0 | 2016-02-17 16:13:46 +0000 | [diff] [blame] | 185 | 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 190 | return c; |
| 191 | } |
| 192 | |
| 193 | /** |
| 194 | * Returns a pretty name for the datatype of object 'o' in the Build language. |
| 195 | */ |
Francois-Rene Rideau | cbebd63 | 2015-02-11 16:56:37 +0000 | [diff] [blame] | 196 | public static String getDataTypeName(Object o) { |
Francois-Rene Rideau | b609107 | 2015-02-25 15:48:30 +0000 | [diff] [blame] | 197 | 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 Rideau | a2c9ac6 | 2016-01-22 10:54:38 +0000 | [diff] [blame] | 204 | public static String getDataTypeName(Object object, boolean fullDetails) { |
Francois-Rene Rideau | b609107 | 2015-02-25 15:48:30 +0000 | [diff] [blame] | 205 | Preconditions.checkNotNull(object); |
Francois-Rene Rideau | a2c9ac6 | 2016-01-22 10:54:38 +0000 | [diff] [blame] | 206 | if (fullDetails) { |
| 207 | if (object instanceof SkylarkNestedSet) { |
| 208 | SkylarkNestedSet set = (SkylarkNestedSet) object; |
Vladimir Moskva | d200daf | 2016-12-23 16:35:37 +0000 | [diff] [blame] | 209 | return "depset of " + set.getContentType() + "s"; |
Francois-Rene Rideau | b609107 | 2015-02-25 15:48:30 +0000 | [diff] [blame] | 210 | } |
Francois-Rene Rideau | a2c9ac6 | 2016-01-22 10:54:38 +0000 | [diff] [blame] | 211 | if (object instanceof SelectorList) { |
| 212 | SelectorList list = (SelectorList) object; |
| 213 | return "select of " + getDataTypeNameFromClass(list.getType()); |
| 214 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 215 | } |
Francois-Rene Rideau | a2c9ac6 | 2016-01-22 10:54:38 +0000 | [diff] [blame] | 216 | return getDataTypeNameFromClass(object.getClass()); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 217 | } |
| 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 Weikert | ee5e5e1 | 2015-08-11 16:47:31 +0000 | [diff] [blame] | 223 | 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 Brandvein | 1171a7f | 2016-10-11 17:51:06 +0000 | [diff] [blame] | 232 | SkylarkModule module = SkylarkInterfaceUtils.getSkylarkModule(c); |
| 233 | if (module != null) { |
| 234 | return module.name() |
Francois-Rene Rideau | 4e99410 | 2015-09-17 22:41:28 +0000 | [diff] [blame] | 235 | + ((module.namespace() && highlightNameSpaces) ? " (a language module)" : ""); |
| 236 | } else if (c.equals(Object.class)) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 237 | 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 Rideau | ab049e0 | 2016-02-17 16:13:46 +0000 | [diff] [blame] | 244 | } 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 Rideau | 95b0d0c | 2015-04-22 16:52:13 +0000 | [diff] [blame] | 248 | } else if (BaseFunction.class.isAssignableFrom(c)) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 249 | return "function"; |
Greg Estren | 92e30d9 | 2015-04-09 21:58:28 +0000 | [diff] [blame] | 250 | } else if (c.equals(SelectorValue.class)) { |
| 251 | return "select"; |
Vladimir Moskva | d200daf | 2016-12-23 16:35:37 +0000 | [diff] [blame] | 252 | } else if (NestedSet.class.isAssignableFrom(c)) { |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame] | 253 | // TODO(bazel-team): no one should be seeing naked NestedSet at all. |
fwe | 3d2c75c | 2017-07-20 15:19:18 +0200 | [diff] [blame] | 254 | return "depset"; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 255 | } else { |
| 256 | if (c.getSimpleName().isEmpty()) { |
| 257 | return c.getName(); |
| 258 | } else { |
| 259 | return c.getSimpleName(); |
| 260 | } |
| 261 | } |
| 262 | } |
| 263 | |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 264 | public static Object checkNotNull(Expression expr, Object obj) throws EvalException { |
| 265 | if (obj == null) { |
Laurent Le Brun | c31f351 | 2016-12-29 21:41:33 +0000 | [diff] [blame] | 266 | 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 272 | } |
| 273 | return obj; |
| 274 | } |
| 275 | |
| 276 | /** |
brandjon | 2b51f78 | 2017-07-25 21:05:04 +0200 | [diff] [blame] | 277 | * Returns the truth value of an object, according to Python rules. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 278 | * http://docs.python.org/2/library/stdtypes.html#truth-value-testing |
| 279 | */ |
| 280 | public static boolean toBoolean(Object o) { |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame] | 281 | if (o == null || o == Runtime.NONE) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 282 | 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 | |
laurentlb | c9b6f4a | 2017-06-21 11:58:50 +0200 | [diff] [blame] | 304 | public static Collection<?> toCollection(Object o, Location loc, @Nullable Environment env) |
| 305 | throws EvalException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 306 | if (o instanceof Collection) { |
Florian Weikert | 233a46e | 2015-12-16 12:38:38 +0000 | [diff] [blame] | 307 | return (Collection<?>) o; |
Laurent Le Brun | 741824b | 2015-03-20 15:10:19 +0000 | [diff] [blame] | 308 | } else if (o instanceof SkylarkList) { |
Laurent Le Brun | 3d776af | 2015-12-23 15:10:29 +0000 | [diff] [blame] | 309 | return ((SkylarkList) o).getImmutableList(); |
Florian Weikert | 233a46e | 2015-12-16 12:38:38 +0000 | [diff] [blame] | 310 | } else if (o instanceof Map) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 311 | // For dictionaries we iterate through the keys only |
Vladimir Moskva | 76e31d1 | 2016-12-05 16:28:37 +0000 | [diff] [blame] | 312 | 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 Brun | 196c1a7 | 2015-03-18 13:03:04 +0000 | [diff] [blame] | 321 | // For determinism, we sort the keys. |
Laurent Le Brun | b54221c | 2015-04-16 16:14:37 +0000 | [diff] [blame] | 322 | try { |
Florian Weikert | 233a46e | 2015-12-16 12:38:38 +0000 | [diff] [blame] | 323 | return SKYLARK_COMPARATOR.sortedCopy(((Map<?, ?>) o).keySet()); |
Laurent Le Brun | b54221c | 2015-04-16 16:14:37 +0000 | [diff] [blame] | 324 | } catch (ComparisonException e) { |
| 325 | throw new EvalException(loc, e); |
| 326 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 327 | } else if (o instanceof SkylarkNestedSet) { |
laurentlb | c9b6f4a | 2017-06-21 11:58:50 +0200 | [diff] [blame] | 328 | return nestedSetToCollection((SkylarkNestedSet) o, loc, env); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 329 | } else { |
| 330 | throw new EvalException(loc, |
Francois-Rene Rideau | cbebd63 | 2015-02-11 16:56:37 +0000 | [diff] [blame] | 331 | "type '" + getDataTypeName(o) + "' is not a collection"); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 332 | } |
| 333 | } |
| 334 | |
laurentlb | c9b6f4a | 2017-06-21 11:58:50 +0200 | [diff] [blame] | 335 | private static Collection<?> nestedSetToCollection( |
| 336 | SkylarkNestedSet set, Location loc, @Nullable Environment env) throws EvalException { |
brandjon | 3c16191 | 2017-10-05 05:06:05 +0200 | [diff] [blame] | 337 | if (env != null && env.getSemantics().incompatibleDepsetIsNotIterable()) { |
laurentlb | c9b6f4a | 2017-06-21 11:58:50 +0200 | [diff] [blame] | 338 | throw new EvalException( |
| 339 | loc, |
laurentlb | c32efc8 | 2017-06-23 16:03:00 +0200 | [diff] [blame] | 340 | "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."); |
laurentlb | c9b6f4a | 2017-06-21 11:58:50 +0200 | [diff] [blame] | 342 | } |
| 343 | return set.toCollection(); |
| 344 | } |
| 345 | |
| 346 | public static Iterable<?> toIterable(Object o, Location loc, @Nullable Environment env) |
| 347 | throws EvalException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 348 | 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. |
laurentlb | a752d8b | 2017-07-12 22:12:04 +0200 | [diff] [blame] | 351 | return split((String) o, loc, env); |
Jon Brandvein | 3cfeeec | 2017-01-20 04:23:37 +0000 | [diff] [blame] | 352 | } else if (o instanceof SkylarkNestedSet) { |
laurentlb | c9b6f4a | 2017-06-21 11:58:50 +0200 | [diff] [blame] | 353 | return nestedSetToCollection((SkylarkNestedSet) o, loc, env); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 354 | } else if (o instanceof Iterable) { |
Florian Weikert | 233a46e | 2015-12-16 12:38:38 +0000 | [diff] [blame] | 355 | return (Iterable<?>) o; |
| 356 | } else if (o instanceof Map) { |
laurentlb | c9b6f4a | 2017-06-21 11:58:50 +0200 | [diff] [blame] | 357 | return toCollection(o, loc, env); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 358 | } else { |
| 359 | throw new EvalException(loc, |
Francois-Rene Rideau | 5f3e30c | 2015-04-10 19:08:39 +0000 | [diff] [blame] | 360 | "type '" + getDataTypeName(o) + "' is not iterable"); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 361 | } |
| 362 | } |
| 363 | |
Jon Brandvein | 3cfeeec | 2017-01-20 04:23:37 +0000 | [diff] [blame] | 364 | /** |
| 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 |
laurentlb | c9b6f4a | 2017-06-21 11:58:50 +0200 | [diff] [blame] | 374 | * unnecessary flattening of depsets. |
| 375 | * <p>TODO(bazel-team): Remove this if/when implicit iteration over {@code SkylarkNestedSet} |
| 376 | * is no longer supported. |
Jon Brandvein | 3cfeeec | 2017-01-20 04:23:37 +0000 | [diff] [blame] | 377 | */ |
| 378 | @Deprecated |
laurentlb | c9b6f4a | 2017-06-21 11:58:50 +0200 | [diff] [blame] | 379 | public static Iterable<?> toIterableStrict(Object o, Location loc, @Nullable Environment env) |
| 380 | throws EvalException { |
Jon Brandvein | 3cfeeec | 2017-01-20 04:23:37 +0000 | [diff] [blame] | 381 | if (o instanceof Iterable) { |
| 382 | return (Iterable<?>) o; |
| 383 | } else if (o instanceof SkylarkNestedSet) { |
laurentlb | c9b6f4a | 2017-06-21 11:58:50 +0200 | [diff] [blame] | 384 | return nestedSetToCollection((SkylarkNestedSet) o, loc, env); |
Jon Brandvein | 3cfeeec | 2017-01-20 04:23:37 +0000 | [diff] [blame] | 385 | } 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 Brandvein | 65e3ae6 | 2016-09-27 19:57:40 +0000 | [diff] [blame] | 392 | 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 | |
laurentlb | a752d8b | 2017-07-12 22:12:04 +0200 | [diff] [blame] | 404 | private static ImmutableList<String> split(String value, Location loc, @Nullable Environment env) |
| 405 | throws EvalException { |
brandjon | 3c16191 | 2017-10-05 05:06:05 +0200 | [diff] [blame] | 406 | if (env != null && env.getSemantics().incompatibleStringIsNotIterable()) { |
laurentlb | a752d8b | 2017-07-12 22:12:04 +0200 | [diff] [blame] | 407 | 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 Weikert | 233a46e | 2015-12-16 12:38:38 +0000 | [diff] [blame] | 413 | 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 420 | /** |
Francois-Rene Rideau | b6ab6f2 | 2015-03-10 16:05:12 +0000 | [diff] [blame] | 421 | * @return the size of the Skylark object or -1 in case the object doesn't have a size. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 422 | */ |
| 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 Brandvein | 3cfeeec | 2017-01-20 04:23:37 +0000 | [diff] [blame] | 429 | 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 433 | } 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 Rideau | b6ab6f2 | 2015-03-10 16:05:12 +0000 | [diff] [blame] | 439 | |
Jon Brandvein | fab8487 | 2016-11-11 16:27:01 +0000 | [diff] [blame] | 440 | // 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 Brun | c31f351 | 2016-12-29 21:41:33 +0000 | [diff] [blame] | 454 | throw new EvalException( |
| 455 | loc, |
| 456 | "index out of range (index is " + index + ", but sequence has " + length + " elements)"); |
Jon Brandvein | fab8487 | 2016-11-11 16:27:01 +0000 | [diff] [blame] | 457 | } |
| 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 Brun | c31f351 | 2016-12-29 21:41:33 +0000 | [diff] [blame] | 467 | throw new EvalException( |
| 468 | loc, "indices must be integers, not " + EvalUtils.getDataTypeName(index)); |
Jon Brandvein | fab8487 | 2016-11-11 16:27:01 +0000 | [diff] [blame] | 469 | } |
| 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 Brun | c31f351 | 2016-12-29 21:41:33 +0000 | [diff] [blame] | 536 | throw new EvalException( |
| 537 | loc, String.format("slice step must be an integer, not '%s'", stepObj)); |
Jon Brandvein | fab8487 | 2016-11-11 16:27:01 +0000 | [diff] [blame] | 538 | } |
| 539 | if (step == 0) { |
Laurent Le Brun | c31f351 | 2016-12-29 21:41:33 +0000 | [diff] [blame] | 540 | throw new EvalException(loc, "slice step cannot be zero"); |
Jon Brandvein | fab8487 | 2016-11-11 16:27:01 +0000 | [diff] [blame] | 541 | } |
| 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 Brun | c31f351 | 2016-12-29 21:41:33 +0000 | [diff] [blame] | 548 | throw new EvalException( |
| 549 | loc, String.format("slice start must be an integer, not '%s'", startObj)); |
Jon Brandvein | fab8487 | 2016-11-11 16:27:01 +0000 | [diff] [blame] | 550 | } |
| 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 Brun | c31f351 | 2016-12-29 21:41:33 +0000 | [diff] [blame] | 558 | throw new EvalException(loc, String.format("slice end must be an integer, not '%s'", endObj)); |
Jon Brandvein | fab8487 | 2016-11-11 16:27:01 +0000 | [diff] [blame] | 559 | } |
| 560 | |
| 561 | return getSliceIndices(start, end, step, length); |
| 562 | } |
| 563 | |
Francois-Rene Rideau | b6ab6f2 | 2015-03-10 16:05:12 +0000 | [diff] [blame] | 564 | /** @return true if x is Java null or Skylark None */ |
| 565 | public static boolean isNullOrNone(Object x) { |
Francois-Rene Rideau | 0f7ba34 | 2015-08-31 16:16:21 +0000 | [diff] [blame] | 566 | return x == null || x == Runtime.NONE; |
Francois-Rene Rideau | b6ab6f2 | 2015-03-10 16:05:12 +0000 | [diff] [blame] | 567 | } |
| 568 | |
| 569 | /** |
Francois-Rene Rideau | ab049e0 | 2016-02-17 16:13:46 +0000 | [diff] [blame] | 570 | * Build a SkylarkDict of kwarg arguments from a list, removing null-s or None-s. |
Francois-Rene Rideau | b6ab6f2 | 2015-03-10 16:05:12 +0000 | [diff] [blame] | 571 | * |
Francois-Rene Rideau | ab049e0 | 2016-02-17 16:13:46 +0000 | [diff] [blame] | 572 | * @param env the Environment in which this map can be mutated. |
Laurent Le Brun | 59fa1c1 | 2015-06-03 14:56:03 +0000 | [diff] [blame] | 573 | * @param init a series of key, value pairs (as consecutive arguments) |
Francois-Rene Rideau | 328f615 | 2016-01-06 19:50:27 +0000 | [diff] [blame] | 574 | * as in {@code optionMap(k1, v1, k2, v2, k3, v3)} |
Laurent Le Brun | 59fa1c1 | 2015-06-03 14:56:03 +0000 | [diff] [blame] | 575 | * where each key is a String, each value is an arbitrary Objet. |
Francois-Rene Rideau | b6ab6f2 | 2015-03-10 16:05:12 +0000 | [diff] [blame] | 576 | * @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 Brun | 59fa1c1 | 2015-06-03 14:56:03 +0000 | [diff] [blame] | 580 | * Ignore any entry where the value is null or None. |
| 581 | * Keys cannot be null. |
Francois-Rene Rideau | b6ab6f2 | 2015-03-10 16:05:12 +0000 | [diff] [blame] | 582 | */ |
| 583 | @SuppressWarnings("unchecked") |
Francois-Rene Rideau | ab049e0 | 2016-02-17 16:13:46 +0000 | [diff] [blame] | 584 | public static <K, V> SkylarkDict<K, V> optionMap(Environment env, Object... init) { |
| 585 | ImmutableMap.Builder<K, V> b = new ImmutableMap.Builder<>(); |
Laurent Le Brun | 59fa1c1 | 2015-06-03 14:56:03 +0000 | [diff] [blame] | 586 | Preconditions.checkState(init.length % 2 == 0); |
| 587 | for (int i = init.length - 2; i >= 0; i -= 2) { |
Francois-Rene Rideau | ab049e0 | 2016-02-17 16:13:46 +0000 | [diff] [blame] | 588 | K key = (K) Preconditions.checkNotNull(init[i]); |
| 589 | V value = (V) init[i + 1]; |
Laurent Le Brun | 59fa1c1 | 2015-06-03 14:56:03 +0000 | [diff] [blame] | 590 | if (!isNullOrNone(value)) { |
Francois-Rene Rideau | b6ab6f2 | 2015-03-10 16:05:12 +0000 | [diff] [blame] | 591 | b.put(key, value); |
| 592 | } |
| 593 | } |
brandjon | c06e746 | 2017-07-11 20:54:58 +0200 | [diff] [blame] | 594 | return SkylarkDict.copyOf(env, b.build()); |
Francois-Rene Rideau | b6ab6f2 | 2015-03-10 16:05:12 +0000 | [diff] [blame] | 595 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 596 | } |