blob: c257922660d611bcc33d2ed7f62ece406e8f220f [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.
14
Francois-Rene Rideau36795562015-07-29 18:50:50 +000015package com.google.devtools.build.lib.syntax;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010016
laurentlb3d2a68c2017-06-30 00:32:04 +020017import static java.util.stream.Collectors.joining;
18
laurentlbe5894f02018-10-25 13:02:00 -070019import com.google.common.base.Ascii;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010020import com.google.common.collect.ImmutableList;
Florian Weikertbc1ff692016-07-01 19:11:24 +000021import com.google.common.collect.ImmutableMap;
laurentlb4a2f8382019-02-18 05:51:03 -080022import com.google.common.collect.Iterables;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010023import com.google.common.collect.Lists;
Francois-Rene Rideauc673a822015-03-02 19:52:39 +000024import com.google.common.collect.Ordering;
cparsonsa7c0afb2019-08-30 08:14:51 -070025import com.google.devtools.build.lib.collect.nestedset.NestedSet.NestedSetDepthException;
cparsonsb36470e2018-06-15 09:04:21 -070026import com.google.devtools.build.lib.collect.nestedset.Order;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010027import com.google.devtools.build.lib.events.Event;
28import com.google.devtools.build.lib.events.Location;
Damien Martin-Guillerez2ca9b722016-06-09 17:43:55 +000029import com.google.devtools.build.lib.skylarkinterface.Param;
cparsons7ac265d2019-04-16 15:31:17 -070030import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
31import com.google.devtools.build.lib.skylarkinterface.SkylarkGlobalLibrary;
John Field585d1a02015-12-16 16:03:52 +000032import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
Dmitry Lomov34cdae32016-06-28 16:13:35 +000033import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
Googler93316322019-11-06 09:26:19 -080034import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
Vladimir Moskva7f0cd622017-02-16 13:48:37 +000035import com.google.devtools.build.lib.syntax.EvalUtils.ComparisonException;
cparsonsf0cf2b42019-08-15 10:56:40 -070036import com.google.devtools.build.lib.syntax.StarlarkSemantics.FlagIdentifier;
brandjondc2c5502017-12-07 14:30:04 -080037import java.util.ArrayDeque;
Googlerc60ec8c2015-03-23 14:20:18 +000038import java.util.ArrayList;
Marwan Tammamcc2c4ee2019-07-16 11:03:48 -070039import java.util.Collections;
40import java.util.Comparator;
Googlerc60ec8c2015-03-23 14:20:18 +000041import java.util.Iterator;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010042import java.util.List;
laurentlb4a2f8382019-02-18 05:51:03 -080043import java.util.Map;
Florian Weikert5e8752b2015-12-11 21:54:43 +000044import java.util.NoSuchElementException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010045import java.util.Set;
46import java.util.TreeSet;
brandjon12b23792017-09-05 21:39:37 +020047import javax.annotation.Nullable;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010048
Laurent Le Brunbd9576a2016-11-18 15:10:51 +000049/** A helper class containing built in functions for the Skylark language. */
cparsons7ac265d2019-04-16 15:31:17 -070050@SkylarkGlobalLibrary
Googler6901c792019-11-07 18:01:49 -080051class MethodLibrary {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010052
cparsons7ac265d2019-04-16 15:31:17 -070053 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -070054 name = "min",
cparsons507b00f2018-09-12 11:59:07 -070055 doc =
56 "Returns the smallest one of all given arguments. "
57 + "If only one argument is provided, it must be a non-empty iterable. "
58 + "It is an error if elements are not comparable (for example int with string). "
59 + "<pre class=\"language-python\">min(2, 5, 4) == 2\n"
60 + "min([5, 6, 3]) == 3</pre>",
61 extraPositionals =
62 @Param(name = "args", type = SkylarkList.class, doc = "The elements to be checked."),
63 useLocation = true,
Googlera3421e22019-09-26 06:48:32 -070064 useStarlarkThread = true)
65 public Object min(SkylarkList<?> args, Location loc, StarlarkThread thread) throws EvalException {
cparsons7ac265d2019-04-16 15:31:17 -070066 try {
Googlera3421e22019-09-26 06:48:32 -070067 return findExtreme(args, EvalUtils.SKYLARK_COMPARATOR.reverse(), loc, thread);
cparsons7ac265d2019-04-16 15:31:17 -070068 } catch (ComparisonException e) {
69 throw new EvalException(loc, e);
70 }
71 }
Florian Weikert5e8752b2015-12-11 21:54:43 +000072
cparsons7ac265d2019-04-16 15:31:17 -070073 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -070074 name = "max",
cparsons507b00f2018-09-12 11:59:07 -070075 doc =
76 "Returns the largest one of all given arguments. "
77 + "If only one argument is provided, it must be a non-empty iterable."
78 + "It is an error if elements are not comparable (for example int with string). "
79 + "<pre class=\"language-python\">max(2, 5, 4) == 5\n"
80 + "max([5, 6, 3]) == 6</pre>",
81 extraPositionals =
82 @Param(name = "args", type = SkylarkList.class, doc = "The elements to be checked."),
83 useLocation = true,
Googlera3421e22019-09-26 06:48:32 -070084 useStarlarkThread = true)
85 public Object max(SkylarkList<?> args, Location loc, StarlarkThread thread) throws EvalException {
cparsons7ac265d2019-04-16 15:31:17 -070086 try {
Googlera3421e22019-09-26 06:48:32 -070087 return findExtreme(args, EvalUtils.SKYLARK_COMPARATOR, loc, thread);
cparsons7ac265d2019-04-16 15:31:17 -070088 } catch (ComparisonException e) {
89 throw new EvalException(loc, e);
90 }
91 }
Florian Weikert5e8752b2015-12-11 21:54:43 +000092
laurentlbc9b6f4a2017-06-21 11:58:50 +020093 /** Returns the maximum element from this list, as determined by maxOrdering. */
94 private static Object findExtreme(
Googlera3421e22019-09-26 06:48:32 -070095 SkylarkList<?> args, Ordering<Object> maxOrdering, Location loc, StarlarkThread thread)
Florian Weikert5e8752b2015-12-11 21:54:43 +000096 throws EvalException {
Jon Brandvein3cfeeec2017-01-20 04:23:37 +000097 // Args can either be a list of items to compare, or a singleton list whose element is an
98 // iterable of items to compare. In either case, there must be at least one item to compare.
Florian Weikert5e8752b2015-12-11 21:54:43 +000099 try {
Googlera3421e22019-09-26 06:48:32 -0700100 Iterable<?> items =
101 (args.size() == 1) ? EvalUtils.toIterable(args.get(0), loc, thread) : args;
Jon Brandvein3cfeeec2017-01-20 04:23:37 +0000102 return maxOrdering.max(items);
Florian Weikert5e8752b2015-12-11 21:54:43 +0000103 } catch (NoSuchElementException ex) {
Googler99cbc172018-11-08 07:43:00 -0800104 throw new EvalException(loc, "expected at least one item", ex);
Florian Weikert5e8752b2015-12-11 21:54:43 +0000105 }
106 }
107
cparsons7ac265d2019-04-16 15:31:17 -0700108 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700109 name = "all",
cparsons507b00f2018-09-12 11:59:07 -0700110 doc =
111 "Returns true if all elements evaluate to True or if the collection is empty. "
112 + "Elements are converted to boolean using the <a href=\"#bool\">bool</a> function."
113 + "<pre class=\"language-python\">all([\"hello\", 3, True]) == True\n"
114 + "all([-1, 0, 1]) == False</pre>",
115 parameters = {
116 @Param(
117 name = "elements",
118 type = Object.class,
cparsons7ac265d2019-04-16 15:31:17 -0700119 noneable = true,
120 doc = "A string or a collection of elements.",
cparsons6bd9a7b2019-04-25 15:39:40 -0700121 // TODO(cparsons): This parameter should be positional-only.
cparsons7ac265d2019-04-16 15:31:17 -0700122 legacyNamed = true)
cparsons507b00f2018-09-12 11:59:07 -0700123 },
124 useLocation = true,
Googlera3421e22019-09-26 06:48:32 -0700125 useStarlarkThread = true)
126 public Boolean all(Object collection, Location loc, StarlarkThread thread) throws EvalException {
127 return !hasElementWithBooleanValue(collection, false, loc, thread);
cparsons7ac265d2019-04-16 15:31:17 -0700128 }
Florian Weikert233a46e2015-12-16 12:38:38 +0000129
cparsons7ac265d2019-04-16 15:31:17 -0700130 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700131 name = "any",
cparsons507b00f2018-09-12 11:59:07 -0700132 doc =
133 "Returns true if at least one element evaluates to True. "
134 + "Elements are converted to boolean using the <a href=\"#bool\">bool</a> function."
135 + "<pre class=\"language-python\">any([-1, 0, 1]) == True\n"
136 + "any([False, 0, \"\"]) == False</pre>",
137 parameters = {
138 @Param(
139 name = "elements",
140 type = Object.class,
cparsons7ac265d2019-04-16 15:31:17 -0700141 noneable = true,
142 doc = "A string or a collection of elements.",
cparsons6bd9a7b2019-04-25 15:39:40 -0700143 // TODO(cparsons): This parameter should be positional-only.
cparsons7ac265d2019-04-16 15:31:17 -0700144 legacyNamed = true)
cparsons507b00f2018-09-12 11:59:07 -0700145 },
146 useLocation = true,
Googlera3421e22019-09-26 06:48:32 -0700147 useStarlarkThread = true)
148 public Boolean any(Object collection, Location loc, StarlarkThread thread) throws EvalException {
149 return hasElementWithBooleanValue(collection, true, loc, thread);
cparsons7ac265d2019-04-16 15:31:17 -0700150 }
Florian Weikert233a46e2015-12-16 12:38:38 +0000151
laurentlbc9b6f4a2017-06-21 11:58:50 +0200152 private static boolean hasElementWithBooleanValue(
Googlera3421e22019-09-26 06:48:32 -0700153 Object collection, boolean value, Location loc, StarlarkThread thread) throws EvalException {
154 Iterable<?> iterable = EvalUtils.toIterable(collection, loc, thread);
Florian Weikert233a46e2015-12-16 12:38:38 +0000155 for (Object obj : iterable) {
Googlere5712242019-11-06 12:34:36 -0800156 if (Starlark.truth(obj) == value) {
Florian Weikert233a46e2015-12-16 12:38:38 +0000157 return true;
158 }
159 }
160 return false;
161 }
162
cparsons7ac265d2019-04-16 15:31:17 -0700163 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700164 name = "sorted",
cparsons507b00f2018-09-12 11:59:07 -0700165 doc =
166 "Sort a collection. Elements should all belong to the same orderable type, they are "
167 + "sorted by their value (in ascending order). "
168 + "It is an error if elements are not comparable (for example int with string)."
169 + "<pre class=\"language-python\">sorted([3, 5, 4]) == [3, 4, 5]</pre>",
cparsons7ac265d2019-04-16 15:31:17 -0700170 parameters = {
cparsons6bd9a7b2019-04-25 15:39:40 -0700171 @Param(
Googler6901c792019-11-07 18:01:49 -0800172 name = "iterable",
cparsons6bd9a7b2019-04-25 15:39:40 -0700173 type = Object.class,
Googler6901c792019-11-07 18:01:49 -0800174 doc = "The iterable sequence to sort.",
cparsons6bd9a7b2019-04-25 15:39:40 -0700175 // TODO(cparsons): This parameter should be positional-only.
Marwan Tammamcc2c4ee2019-07-16 11:03:48 -0700176 legacyNamed = true),
177 @Param(
178 name = "key",
179 doc = "An optional function applied to each element before comparison.",
180 named = true,
181 defaultValue = "None",
182 positional = false,
183 noneable = true),
184 @Param(
185 name = "reverse",
186 type = Boolean.class,
187 doc = "Return results in descending order.",
188 named = true,
189 defaultValue = "False",
cparsonsa330feb2019-10-30 09:17:48 -0700190 positional = false)
cparsons7ac265d2019-04-16 15:31:17 -0700191 },
cparsons507b00f2018-09-12 11:59:07 -0700192 useLocation = true,
Googlera3421e22019-09-26 06:48:32 -0700193 useStarlarkThread = true)
Googler942e1c42019-11-12 13:11:44 -0800194 public StarlarkList<?> sorted(
Googler6901c792019-11-07 18:01:49 -0800195 Object iterable,
Googlera3421e22019-09-26 06:48:32 -0700196 final Object key,
197 Boolean reverse,
198 final Location loc,
199 final StarlarkThread thread)
Marwan Tammamcc2c4ee2019-07-16 11:03:48 -0700200 throws EvalException, InterruptedException {
201
Googler6901c792019-11-07 18:01:49 -0800202 ArrayList<?> list = new ArrayList<>(EvalUtils.toCollection(iterable, loc, thread));
Googler641bdf72019-11-12 10:32:26 -0800203 if (key == Starlark.NONE) {
Marwan Tammamcc2c4ee2019-07-16 11:03:48 -0700204 try {
205 Collections.sort(list, EvalUtils.SKYLARK_COMPARATOR);
206 } catch (EvalUtils.ComparisonException e) {
207 throw new EvalException(loc, e);
208 }
Googler10167a82019-08-27 11:43:28 -0700209 } else if (key instanceof StarlarkCallable) {
210 final StarlarkCallable keyfn = (StarlarkCallable) key;
Marwan Tammamcc2c4ee2019-07-16 11:03:48 -0700211 final FuncallExpression ast = new FuncallExpression(Identifier.of(""), ImmutableList.of());
212
213 class KeyComparator implements Comparator<Object> {
214 Exception e;
215
216 @Override
217 public int compare(Object x, Object y) {
218 try {
Googler327bd662019-07-16 14:41:24 -0700219 return EvalUtils.SKYLARK_COMPARATOR.compare(callKeyFunc(x), callKeyFunc(y));
Marwan Tammamcc2c4ee2019-07-16 11:03:48 -0700220 } catch (InterruptedException | EvalException e) {
221 if (this.e == null) {
222 this.e = e;
223 }
224 return 0;
225 }
226 }
Googler327bd662019-07-16 14:41:24 -0700227
228 Object callKeyFunc(Object x) throws EvalException, InterruptedException {
Googlera3421e22019-09-26 06:48:32 -0700229 return keyfn.call(Collections.singletonList(x), ImmutableMap.of(), ast, thread);
Googler327bd662019-07-16 14:41:24 -0700230 }
Marwan Tammamcc2c4ee2019-07-16 11:03:48 -0700231 }
232
233 KeyComparator comp = new KeyComparator();
234 try {
235 Collections.sort(list, comp);
236 } catch (EvalUtils.ComparisonException e) {
237 throw new EvalException(loc, e);
238 }
239
240 if (comp.e != null) {
241 if (comp.e instanceof InterruptedException) {
242 throw (InterruptedException) comp.e;
243 }
244 throw (EvalException) comp.e;
245 }
Googler327bd662019-07-16 14:41:24 -0700246 } else {
247 throw new EvalException(
248 loc, Printer.format("%r object is not callable", EvalUtils.getDataTypeName(key)));
cparsons7ac265d2019-04-16 15:31:17 -0700249 }
Marwan Tammamcc2c4ee2019-07-16 11:03:48 -0700250
251 if (reverse) {
252 Collections.reverse(list);
253 }
Googler942e1c42019-11-12 13:11:44 -0800254 return StarlarkList.wrapUnsafe(thread, list);
cparsons7ac265d2019-04-16 15:31:17 -0700255 }
Laurent Le Brunef69ec52015-04-16 18:58:34 +0000256
cparsons7ac265d2019-04-16 15:31:17 -0700257 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700258 name = "reversed",
cparsons507b00f2018-09-12 11:59:07 -0700259 doc =
260 "Returns a list that contains the elements of the original sequence in reversed order."
261 + "<pre class=\"language-python\">reversed([3, 5, 4]) == [4, 5, 3]</pre>",
262 parameters = {
263 @Param(
264 name = "sequence",
265 type = Object.class,
cparsons7ac265d2019-04-16 15:31:17 -0700266 doc = "The sequence to be reversed (string, list or tuple).",
cparsons6bd9a7b2019-04-25 15:39:40 -0700267 // TODO(cparsons): This parameter should be positional-only.
cparsons7ac265d2019-04-16 15:31:17 -0700268 legacyNamed = true),
cparsons507b00f2018-09-12 11:59:07 -0700269 },
270 useLocation = true,
Googlera3421e22019-09-26 06:48:32 -0700271 useStarlarkThread = true)
Googler942e1c42019-11-12 13:11:44 -0800272 public StarlarkList<?> reversed(Object sequence, Location loc, StarlarkThread thread)
cparsons7ac265d2019-04-16 15:31:17 -0700273 throws EvalException {
cparsons7ac265d2019-04-16 15:31:17 -0700274 if (sequence instanceof SkylarkDict) {
275 throw new EvalException(loc, "Argument to reversed() must be a sequence, not a dictionary.");
cparsons7ac265d2019-04-16 15:31:17 -0700276 }
277 ArrayDeque<Object> tmpList = new ArrayDeque<>();
Googlera3421e22019-09-26 06:48:32 -0700278 for (Object element : EvalUtils.toIterable(sequence, loc, thread)) {
cparsons7ac265d2019-04-16 15:31:17 -0700279 tmpList.addFirst(element);
280 }
Googler942e1c42019-11-12 13:11:44 -0800281 return StarlarkList.copyOf(thread, tmpList);
cparsons7ac265d2019-04-16 15:31:17 -0700282 }
Florian Weikertd5e33502015-12-14 12:06:10 +0000283
cparsons7ac265d2019-04-16 15:31:17 -0700284 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700285 name = "tuple",
cparsons507b00f2018-09-12 11:59:07 -0700286 doc =
287 "Converts a collection (e.g. list, tuple or dictionary) to a tuple."
288 + "<pre class=\"language-python\">tuple([1, 2]) == (1, 2)\n"
289 + "tuple((2, 3, 2)) == (2, 3, 2)\n"
290 + "tuple({5: \"a\", 2: \"b\", 4: \"c\"}) == (5, 2, 4)</pre>",
cparsons6bd9a7b2019-04-25 15:39:40 -0700291 parameters = {
292 @Param(
293 name = "x",
Marwan Tammame1e9ec22019-07-09 05:08:37 -0700294 defaultValue = "()",
cparsons6bd9a7b2019-04-25 15:39:40 -0700295 doc = "The object to convert.",
296 // TODO(cparsons): This parameter should be positional-only.
297 legacyNamed = true)
298 },
cparsons507b00f2018-09-12 11:59:07 -0700299 useLocation = true,
Googlera3421e22019-09-26 06:48:32 -0700300 useStarlarkThread = true)
301 public Tuple<?> tuple(Object x, Location loc, StarlarkThread thread) throws EvalException {
302 return Tuple.copyOf(EvalUtils.toCollection(x, loc, thread));
cparsons7ac265d2019-04-16 15:31:17 -0700303 }
Googlerde689132016-12-12 18:15:52 +0000304
cparsons7ac265d2019-04-16 15:31:17 -0700305 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700306 name = "list",
cparsons507b00f2018-09-12 11:59:07 -0700307 doc =
308 "Converts a collection (e.g. list, tuple or dictionary) to a list."
309 + "<pre class=\"language-python\">list([1, 2]) == [1, 2]\n"
310 + "list((2, 3, 2)) == [2, 3, 2]\n"
311 + "list({5: \"a\", 2: \"b\", 4: \"c\"}) == [5, 2, 4]</pre>",
cparsons6bd9a7b2019-04-25 15:39:40 -0700312 parameters = {
313 @Param(
314 name = "x",
Marwan Tammame1e9ec22019-07-09 05:08:37 -0700315 defaultValue = "[]",
cparsons6bd9a7b2019-04-25 15:39:40 -0700316 doc = "The object to convert.",
317 // TODO(cparsons): This parameter should be positional-only.
318 legacyNamed = true)
319 },
cparsons507b00f2018-09-12 11:59:07 -0700320 useLocation = true,
Googlera3421e22019-09-26 06:48:32 -0700321 useStarlarkThread = true)
Googler942e1c42019-11-12 13:11:44 -0800322 public StarlarkList<?> list(Object x, Location loc, StarlarkThread thread) throws EvalException {
323 return StarlarkList.copyOf(thread, EvalUtils.toCollection(x, loc, thread));
cparsons7ac265d2019-04-16 15:31:17 -0700324 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100325
cparsons7ac265d2019-04-16 15:31:17 -0700326 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700327 name = "len",
cparsons507b00f2018-09-12 11:59:07 -0700328 doc = "Returns the length of a string, list, tuple, depset, or dictionary.",
cparsons6bd9a7b2019-04-25 15:39:40 -0700329 parameters = {
330 @Param(
331 name = "x",
332 doc = "The object to check length of.",
333 // TODO(cparsons): This parameter should be positional-only.
334 legacyNamed = true)
335 },
cparsons507b00f2018-09-12 11:59:07 -0700336 useLocation = true,
Googlera3421e22019-09-26 06:48:32 -0700337 useStarlarkThread = true)
338 public Integer len(Object x, Location loc, StarlarkThread thread) throws EvalException {
cparsons7ac265d2019-04-16 15:31:17 -0700339 if (x instanceof String) {
340 return ((String) x).length();
341 } else if (x instanceof Map) {
342 return ((Map<?, ?>) x).size();
343 } else if (x instanceof SkylarkList) {
344 return ((SkylarkList<?>) x).size();
345 } else if (x instanceof SkylarkNestedSet) {
Googlera3421e22019-09-26 06:48:32 -0700346 if (thread.getSemantics().incompatibleDepsetIsNotIterable()) {
cparsons7ac265d2019-04-16 15:31:17 -0700347 throw new EvalException(
348 loc,
349 EvalUtils.getDataTypeName(x)
350 + " is not iterable. You may use `len(<depset>.to_list())` instead. Use "
351 + "--incompatible_depset_is_not_iterable=false to temporarily disable this "
352 + "check.");
353 }
354 return ((SkylarkNestedSet) x).toCollection().size();
355 } else if (x instanceof Iterable) {
356 // Iterables.size() checks if x is a Collection so it's efficient in that sense.
357 return Iterables.size((Iterable<?>) x);
358 } else {
359 throw new EvalException(loc, EvalUtils.getDataTypeName(x) + " is not iterable");
360 }
361 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100362
cparsons7ac265d2019-04-16 15:31:17 -0700363 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700364 name = "str",
cparsons507b00f2018-09-12 11:59:07 -0700365 doc =
366 "Converts any object to string. This is useful for debugging."
367 + "<pre class=\"language-python\">str(\"ab\") == \"ab\"\n"
368 + "str(8) == \"8\"</pre>",
cparsons7ac265d2019-04-16 15:31:17 -0700369 parameters = {
cparsons6bd9a7b2019-04-25 15:39:40 -0700370 @Param(
371 name = "x",
372 doc = "The object to convert.",
373 // TODO(cparsons): This parameter should be positional-only.
374 legacyNamed = true,
375 noneable = true)
cparsonsa7c0afb2019-08-30 08:14:51 -0700376 },
377 useLocation = true)
378 public String str(Object x, Location loc) throws EvalException {
379 try {
380 return Printer.str(x);
381 } catch (NestedSetDepthException exception) {
382 throw new EvalException(
383 loc,
384 "depset exceeded maximum depth "
385 + exception.getDepthLimit()
386 + ". This was only discovered when attempting to flatten the depset for str(), as "
387 + "the size of depsets is unknown until flattening. "
388 + "See https://github.com/bazelbuild/bazel/issues/9180 for details and possible "
389 + "solutions.");
390 }
cparsons7ac265d2019-04-16 15:31:17 -0700391 }
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000392
cparsons7ac265d2019-04-16 15:31:17 -0700393 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700394 name = "repr",
cparsons507b00f2018-09-12 11:59:07 -0700395 doc =
396 "Converts any object to a string representation. This is useful for debugging.<br>"
397 + "<pre class=\"language-python\">repr(\"ab\") == '\"ab\"'</pre>",
cparsons7ac265d2019-04-16 15:31:17 -0700398 parameters = {
cparsons6bd9a7b2019-04-25 15:39:40 -0700399 @Param(
400 name = "x",
401 doc = "The object to convert.",
402 // TODO(cparsons): This parameter should be positional-only.
403 legacyNamed = true,
404 noneable = true)
cparsons7ac265d2019-04-16 15:31:17 -0700405 })
406 public String repr(Object x) {
407 return Printer.repr(x);
408 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100409
cparsons7ac265d2019-04-16 15:31:17 -0700410 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700411 name = "bool",
cparsons507b00f2018-09-12 11:59:07 -0700412 doc =
413 "Constructor for the bool type. "
414 + "It returns <code>False</code> if the object is <code>None</code>, <code>False"
415 + "</code>, an empty string (<code>\"\"</code>), the number <code>0</code>, or an "
416 + "empty collection (e.g. <code>()</code>, <code>[]</code>). "
417 + "Otherwise, it returns <code>True</code>.",
cparsons7ac265d2019-04-16 15:31:17 -0700418 parameters = {
cparsons6bd9a7b2019-04-25 15:39:40 -0700419 @Param(
420 name = "x",
Marwan Tammame1e9ec22019-07-09 05:08:37 -0700421 defaultValue = "False",
cparsons6bd9a7b2019-04-25 15:39:40 -0700422 doc = "The variable to convert.",
423 // TODO(cparsons): This parameter should be positional-only.
424 legacyNamed = true,
425 noneable = true)
cparsons7ac265d2019-04-16 15:31:17 -0700426 })
427 public Boolean bool(Object x) throws EvalException {
Googlere5712242019-11-06 12:34:36 -0800428 return Starlark.truth(x);
cparsons7ac265d2019-04-16 15:31:17 -0700429 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100430
cparsons7ac265d2019-04-16 15:31:17 -0700431 private final ImmutableMap<String, Integer> intPrefixes =
432 ImmutableMap.of("0b", 2, "0o", 8, "0x", 16);
433
434 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700435 name = "int",
cparsons507b00f2018-09-12 11:59:07 -0700436 doc =
437 "Returns x as an int value."
438 + "<ul>"
439 + "<li>If <code>x</code> is already an int, it is returned as-is."
440 + "<li>If <code>x</code> is a boolean, a true value returns 1 and a false value "
441 + " returns 0."
442 + "<li>If <code>x</code> is a string, it must have the format "
443 + " <code>&lt;sign&gt;&lt;prefix&gt;&lt;digits&gt;</code>. "
444 + " <code>&lt;sign&gt;</code> is either <code>\"+\"</code>, <code>\"-\"</code>, "
445 + " or empty (interpreted as positive). <code>&lt;digits&gt;</code> are a "
446 + " sequence of digits from 0 up to <code>base</code> - 1, where the letters a-z "
447 + " (or equivalently, A-Z) are used as digits for 10-35. In the case where "
448 + " <code>base</code> is 2/8/16, <code>&lt;prefix&gt;</code> is optional and may "
449 + " be 0b/0o/0x (or equivalently, 0B/0O/0X) respectively; if the "
450 + " <code>base</code> is any other value besides these bases or the special value "
451 + " 0, the prefix must be empty. In the case where <code>base</code> is 0, the "
452 + " string is interpreted as an integer literal, in the sense that one of the "
453 + " bases 2/8/10/16 is chosen depending on which prefix if any is used. If "
454 + " <code>base</code> is 0, no prefix is used, and there is more than one digit, "
455 + " the leading digit cannot be 0; this is to avoid confusion between octal and "
456 + " decimal. The magnitude of the number represented by the string must be within "
457 + " the allowed range for the int type."
458 + "</ul>"
459 + "This function fails if <code>x</code> is any other type, or if the value is a "
460 + "string not satisfying the above format. Unlike Python's <code>int()</code> "
461 + "function, this function does not allow zero arguments, and does not allow "
462 + "extraneous whitespace for string arguments."
463 + "<p>Examples:"
464 + "<pre class=\"language-python\">"
465 + "int(\"123\") == 123\n"
466 + "int(\"-123\") == -123\n"
467 + "int(\"+123\") == 123\n"
468 + "int(\"FF\", 16) == 255\n"
469 + "int(\"0xFF\", 16) == 255\n"
470 + "int(\"10\", 0) == 10\n"
471 + "int(\"-0x10\", 0) == -16"
472 + "</pre>",
473 parameters = {
cparsons6bd9a7b2019-04-25 15:39:40 -0700474 @Param(
475 name = "x",
476 type = Object.class,
477 doc = "The string to convert.",
478 // TODO(cparsons): This parameter should be positional-only.
479 legacyNamed = true),
cparsons507b00f2018-09-12 11:59:07 -0700480 @Param(
481 name = "base",
482 type = Object.class,
483 defaultValue = "unbound",
484 doc =
485 "The base used to interpret a string value; defaults to 10. Must be between 2 "
486 + "and 36 (inclusive), or 0 to detect the base as if <code>x</code> were an "
487 + "integer literal. This parameter must not be supplied if the value is not a "
cparsons7ac265d2019-04-16 15:31:17 -0700488 + "string.",
cparsons6bd9a7b2019-04-25 15:39:40 -0700489 named = true)
cparsons507b00f2018-09-12 11:59:07 -0700490 },
491 useLocation = true)
cparsons7ac265d2019-04-16 15:31:17 -0700492 public Integer convertToInt(Object x, Object base, Location loc) throws EvalException {
493 if (x instanceof String) {
Googlerf7e471b2019-11-11 10:10:07 -0800494 if (base == Starlark.UNBOUND) {
cparsons7ac265d2019-04-16 15:31:17 -0700495 base = 10;
496 } else if (!(base instanceof Integer)) {
497 throw new EvalException(
498 loc, "base must be an integer (got '" + EvalUtils.getDataTypeName(base) + "')");
499 }
500 return fromString((String) x, loc, (Integer) base);
501 } else {
Googlerf7e471b2019-11-11 10:10:07 -0800502 if (base != Starlark.UNBOUND) {
cparsons7ac265d2019-04-16 15:31:17 -0700503 throw new EvalException(loc, "int() can't convert non-string with explicit base");
504 }
505 if (x instanceof Boolean) {
506 return ((Boolean) x).booleanValue() ? 1 : 0;
507 } else if (x instanceof Integer) {
508 return (Integer) x;
509 }
510 throw new EvalException(loc, Printer.format("%r is not of type string or int or bool", x));
511 }
512 }
Florian Weikertbc1ff692016-07-01 19:11:24 +0000513
cparsons7ac265d2019-04-16 15:31:17 -0700514 private int fromString(String string, Location loc, int base) throws EvalException {
515 String stringForErrors = string;
516
517 boolean isNegative = false;
518 if (string.isEmpty()) {
519 throw new EvalException(loc, Printer.format("string argument to int() cannot be empty"));
520 }
521 char c = string.charAt(0);
522 if (c == '+') {
523 string = string.substring(1);
524 } else if (c == '-') {
525 string = string.substring(1);
526 isNegative = true;
527 }
528
529 String prefix = getIntegerPrefix(string);
530 String digits;
531 if (prefix == null) {
532 // Nothing to strip. Infer base 10 if autodetection was requested (base == 0).
533 digits = string;
534 if (base == 0) {
535 if (string.length() > 1 && string.startsWith("0")) {
536 // We don't infer the base when input starts with '0' (due
537 // to confusion between octal and decimal).
538 throw new EvalException(
539 loc,
540 Printer.format(
541 "cannot infer base for int() when value begins with a 0: %r", stringForErrors));
Laurent Le Brunf4648de2015-05-07 14:00:32 +0000542 }
cparsons7ac265d2019-04-16 15:31:17 -0700543 base = 10;
544 }
545 } else {
546 // Strip prefix. Infer base from prefix if unknown (base == 0), or else verify its
547 // consistency.
548 digits = string.substring(prefix.length());
549 int expectedBase = intPrefixes.get(prefix);
550 if (base == 0) {
551 base = expectedBase;
552 } else if (base != expectedBase) {
553 throw new EvalException(
554 loc,
555 Printer.format("invalid literal for int() with base %d: %r", base, stringForErrors));
556 }
557 }
Florian Weikertbc1ff692016-07-01 19:11:24 +0000558
cparsons7ac265d2019-04-16 15:31:17 -0700559 if (base < 2 || base > 36) {
560 throw new EvalException(loc, "int() base must be >= 2 and <= 36");
561 }
562 try {
563 // Negate by prepending a negative symbol, rather than by using arithmetic on the
564 // result, to handle the edge case of -2^31 correctly.
565 String parseable = isNegative ? "-" + digits : digits;
566 return Integer.parseInt(parseable, base);
567 } catch (NumberFormatException | ArithmeticException e) {
568 throw new EvalException(
569 loc,
570 Printer.format("invalid literal for int() with base %d: %r", base, stringForErrors),
571 e);
572 }
573 }
brandjond58bd262018-03-23 07:29:26 -0700574
cparsons7ac265d2019-04-16 15:31:17 -0700575 @Nullable
576 private String getIntegerPrefix(String value) {
577 value = Ascii.toLowerCase(value);
578 for (String prefix : intPrefixes.keySet()) {
579 if (value.startsWith(prefix)) {
580 return prefix;
581 }
582 }
583 return null;
584 }
brandjond58bd262018-03-23 07:29:26 -0700585
cparsons7ac265d2019-04-16 15:31:17 -0700586 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700587 name = "dict",
cparsons507b00f2018-09-12 11:59:07 -0700588 doc =
589 "Creates a <a href=\"dict.html\">dictionary</a> from an optional positional "
590 + "argument and an optional set of keyword arguments. In the case where the same key "
591 + "is given multiple times, the last value will be used. Entries supplied via "
592 + "keyword arguments are considered to come after entries supplied via the "
593 + "positional argument.",
594 parameters = {
595 @Param(
596 name = "args",
597 type = Object.class,
598 defaultValue = "[]",
599 doc =
600 "Either a dictionary or a list of entries. Entries must be tuples or lists with "
cparsons7ac265d2019-04-16 15:31:17 -0700601 + "exactly two elements: key, value.",
cparsons6bd9a7b2019-04-25 15:39:40 -0700602 // TODO(cparsons): This parameter should be positional-only.
cparsons7ac265d2019-04-16 15:31:17 -0700603 legacyNamed = true),
cparsons507b00f2018-09-12 11:59:07 -0700604 },
605 extraKeywords = @Param(name = "kwargs", doc = "Dictionary of additional entries."),
606 useLocation = true,
Googlera3421e22019-09-26 06:48:32 -0700607 useStarlarkThread = true)
cparsons7ac265d2019-04-16 15:31:17 -0700608 public SkylarkDict<?, ?> dict(
Googlera3421e22019-09-26 06:48:32 -0700609 Object args, SkylarkDict<?, ?> kwargs, Location loc, StarlarkThread thread)
610 throws EvalException {
cparsons7ac265d2019-04-16 15:31:17 -0700611 SkylarkDict<?, ?> argsDict =
Googlerc5fcc862019-09-06 16:17:47 -0700612 args instanceof SkylarkDict
613 ? (SkylarkDict) args
Googlera3421e22019-09-26 06:48:32 -0700614 : SkylarkDict.getDictFromArgs("dict", args, loc, thread);
615 return SkylarkDict.plus(argsDict, kwargs, thread);
cparsons7ac265d2019-04-16 15:31:17 -0700616 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100617
cparsons7ac265d2019-04-16 15:31:17 -0700618 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700619 name = "enumerate",
cparsons507b00f2018-09-12 11:59:07 -0700620 doc =
621 "Returns a list of pairs (two-element tuples), with the index (int) and the item from"
Marwan Tammam4863ab12019-08-19 09:28:19 -0700622 + " the input sequence.\n<pre class=\"language-python\">"
cparsons507b00f2018-09-12 11:59:07 -0700623 + "enumerate([24, 21, 84]) == [(0, 24), (1, 21), (2, 84)]</pre>\n",
cparsons7ac265d2019-04-16 15:31:17 -0700624 parameters = {
cparsons6bd9a7b2019-04-25 15:39:40 -0700625 // Note Python uses 'sequence' keyword instead of 'list'. We may want to change tihs
626 // some day.
Marwan Tammam4863ab12019-08-19 09:28:19 -0700627 @Param(name = "list", type = Object.class, doc = "input sequence.", named = true),
Marwan Tammam50f30a52019-07-09 10:39:41 -0700628 @Param(
629 name = "start",
630 type = Integer.class,
631 doc = "start index.",
632 defaultValue = "0",
633 named = true)
cparsons7ac265d2019-04-16 15:31:17 -0700634 },
Googlera3421e22019-09-26 06:48:32 -0700635 useStarlarkThread = true,
Marwan Tammam4863ab12019-08-19 09:28:19 -0700636 useLocation = true)
Googler942e1c42019-11-12 13:11:44 -0800637 public StarlarkList<?> enumerate(Object input, Integer start, Location loc, StarlarkThread thread)
Marwan Tammam50f30a52019-07-09 10:39:41 -0700638 throws EvalException {
639 int count = start;
Marwan Tammam4863ab12019-08-19 09:28:19 -0700640 ArrayList<SkylarkList<?>> result = new ArrayList<>();
Googlera3421e22019-09-26 06:48:32 -0700641 for (Object obj : EvalUtils.toCollection(input, loc, thread)) {
cparsons7ac265d2019-04-16 15:31:17 -0700642 result.add(Tuple.of(count, obj));
643 count++;
644 }
Googler942e1c42019-11-12 13:11:44 -0800645 return StarlarkList.wrapUnsafe(thread, result);
cparsons7ac265d2019-04-16 15:31:17 -0700646 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100647
cparsons7ac265d2019-04-16 15:31:17 -0700648 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700649 name = "hash",
cparsons507b00f2018-09-12 11:59:07 -0700650 doc =
651 "Return a hash value for a string. This is computed deterministically using the same "
652 + "algorithm as Java's <code>String.hashCode()</code>, namely: "
653 + "<pre class=\"language-python\">s[0] * (31^(n-1)) + s[1] * (31^(n-2)) + ... + "
654 + "s[n-1]</pre> Hashing of values besides strings is not currently supported.",
655 // Deterministic hashing is important for the consistency of builds, hence why we
656 // promise a specific algorithm. This is in contrast to Java (Object.hashCode()) and
657 // Python, which promise stable hashing only within a given execution of the program.
cparsons7ac265d2019-04-16 15:31:17 -0700658 parameters = {
659 @Param(
660 name = "value",
661 type = String.class,
662 doc = "String value to hash.",
cparsons6bd9a7b2019-04-25 15:39:40 -0700663 // TODO(cparsons): This parameter should be positional-only.
cparsons7ac265d2019-04-16 15:31:17 -0700664 legacyNamed = true)
665 })
666 public Integer hash(String value) throws EvalException {
667 return value.hashCode();
668 }
Jon Brandvein9c4629d2016-07-20 20:16:33 +0000669
cparsons7ac265d2019-04-16 15:31:17 -0700670 @SkylarkCallable(
Taras Tsugrii9de215d2018-07-17 08:56:13 -0700671 name = "range",
Taras Tsugrii9de215d2018-07-17 08:56:13 -0700672 doc =
673 "Creates a list where items go from <code>start</code> to <code>stop</code>, using a "
674 + "<code>step</code> increment. If a single argument is provided, items will "
675 + "range from 0 to that element."
676 + "<pre class=\"language-python\">range(4) == [0, 1, 2, 3]\n"
677 + "range(3, 9, 2) == [3, 5, 7]\n"
678 + "range(3, 0, -1) == [3, 2, 1]</pre>",
679 parameters = {
680 @Param(
681 name = "start_or_stop",
682 type = Integer.class,
683 doc =
684 "Value of the start element if stop is provided, "
cparsons7ac265d2019-04-16 15:31:17 -0700685 + "otherwise value of stop and the actual start is 0",
cparsons6bd9a7b2019-04-25 15:39:40 -0700686 // TODO(cparsons): This parameter should be positional-only.
cparsons7ac265d2019-04-16 15:31:17 -0700687 legacyNamed = true),
Taras Tsugrii9de215d2018-07-17 08:56:13 -0700688 @Param(
689 name = "stop_or_none",
690 type = Integer.class,
691 noneable = true,
692 defaultValue = "None",
693 doc =
694 "optional index of the first item <i>not</i> to be included in the resulting "
cparsons7ac265d2019-04-16 15:31:17 -0700695 + "list; generation of the list stops before <code>stop</code> is reached.",
cparsons6bd9a7b2019-04-25 15:39:40 -0700696 // TODO(cparsons): This parameter should be positional-only.
cparsons7ac265d2019-04-16 15:31:17 -0700697 legacyNamed = true),
Taras Tsugrii9de215d2018-07-17 08:56:13 -0700698 @Param(
699 name = "step",
700 type = Integer.class,
701 defaultValue = "1",
cparsons7ac265d2019-04-16 15:31:17 -0700702 doc = "The increment (default is 1). It may be negative.",
cparsons6bd9a7b2019-04-25 15:39:40 -0700703 // TODO(cparsons): This parameter should be positional-only.
cparsons7ac265d2019-04-16 15:31:17 -0700704 legacyNamed = true)
Taras Tsugrii9de215d2018-07-17 08:56:13 -0700705 },
706 useLocation = true,
Googlera3421e22019-09-26 06:48:32 -0700707 useStarlarkThread = true)
cparsons7ac265d2019-04-16 15:31:17 -0700708 public SkylarkList<Integer> range(
Googlera3421e22019-09-26 06:48:32 -0700709 Integer startOrStop, Object stopOrNone, Integer step, Location loc, StarlarkThread thread)
cparsons7ac265d2019-04-16 15:31:17 -0700710 throws EvalException {
711 int start;
712 int stop;
Googler641bdf72019-11-12 10:32:26 -0800713 if (stopOrNone == Starlark.NONE) {
cparsons7ac265d2019-04-16 15:31:17 -0700714 start = 0;
715 stop = startOrStop;
Googlerc5fcc862019-09-06 16:17:47 -0700716 } else if (stopOrNone instanceof Integer) {
cparsons7ac265d2019-04-16 15:31:17 -0700717 start = startOrStop;
Googlerc5fcc862019-09-06 16:17:47 -0700718 stop = (Integer) stopOrNone;
719 } else {
720 throw new EvalException(loc, "want int, got " + EvalUtils.getDataTypeName(stopOrNone));
cparsons7ac265d2019-04-16 15:31:17 -0700721 }
722 if (step == 0) {
723 throw new EvalException(loc, "step cannot be 0");
724 }
725 return RangeList.of(start, stop, step);
726 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100727
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000728 /** Returns true if the object has a field of the given name, otherwise false. */
cparsons7ac265d2019-04-16 15:31:17 -0700729 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700730 name = "hasattr",
cparsons507b00f2018-09-12 11:59:07 -0700731 doc =
732 "Returns True if the object <code>x</code> has an attribute or method of the given "
733 + "<code>name</code>, otherwise False. Example:<br>"
734 + "<pre class=\"language-python\">hasattr(ctx.attr, \"myattr\")</pre>",
735 parameters = {
cparsons6bd9a7b2019-04-25 15:39:40 -0700736 @Param(
737 name = "x",
738 doc = "The object to check.",
739 // TODO(cparsons): This parameter should be positional-only.
740 legacyNamed = true,
741 noneable = true),
cparsons7ac265d2019-04-16 15:31:17 -0700742 @Param(
743 name = "name",
744 type = String.class,
745 doc = "The name of the attribute.",
cparsons6bd9a7b2019-04-25 15:39:40 -0700746 // TODO(cparsons): This parameter should be positional-only.
cparsons7ac265d2019-04-16 15:31:17 -0700747 legacyNamed = true)
cparsons507b00f2018-09-12 11:59:07 -0700748 },
Googlera3421e22019-09-26 06:48:32 -0700749 useStarlarkThread = true)
750 public Boolean hasAttr(Object obj, String name, StarlarkThread thread) throws EvalException {
cparsons7ac265d2019-04-16 15:31:17 -0700751 if (obj instanceof ClassObject && ((ClassObject) obj).getValue(name) != null) {
752 return true;
753 }
754 // shouldn't this filter things with struct_field = false?
Googlera3421e22019-09-26 06:48:32 -0700755 return EvalUtils.hasMethod(thread.getSemantics(), obj, name);
cparsons7ac265d2019-04-16 15:31:17 -0700756 }
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000757
cparsons7ac265d2019-04-16 15:31:17 -0700758 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700759 name = "getattr",
760 doc =
761 "Returns the struct's field of the given name if it exists. If not, it either returns "
Marwan Tammama7b2b112019-07-24 03:45:55 -0700762 + "<code>default</code> (if specified) or raises an error. "
763 + "<code>getattr(x, \"foobar\")</code> is equivalent to <code>x.foobar</code>."
cparsons507b00f2018-09-12 11:59:07 -0700764 + "<pre class=\"language-python\">getattr(ctx.attr, \"myattr\")\n"
765 + "getattr(ctx.attr, \"myattr\", \"mydefault\")</pre>",
766 parameters = {
cparsons7ac265d2019-04-16 15:31:17 -0700767 @Param(
768 name = "x",
769 doc = "The struct whose attribute is accessed.",
cparsons6bd9a7b2019-04-25 15:39:40 -0700770 // TODO(cparsons): This parameter should be positional-only.
cparsons7ac265d2019-04-16 15:31:17 -0700771 legacyNamed = true,
772 noneable = true),
cparsons6bd9a7b2019-04-25 15:39:40 -0700773 @Param(
774 name = "name",
775 doc = "The name of the struct attribute.",
776 // TODO(cparsons): This parameter should be positional-only.
777 legacyNamed = true),
cparsons507b00f2018-09-12 11:59:07 -0700778 @Param(
779 name = "default",
780 defaultValue = "unbound",
781 doc =
782 "The default value to return in case the struct "
cparsons7ac265d2019-04-16 15:31:17 -0700783 + "doesn't have an attribute of the given name.",
cparsons6bd9a7b2019-04-25 15:39:40 -0700784 // TODO(cparsons): This parameter should be positional-only.
cparsons7ac265d2019-04-16 15:31:17 -0700785 legacyNamed = true,
786 noneable = true)
cparsons507b00f2018-09-12 11:59:07 -0700787 },
788 useLocation = true,
Googlera3421e22019-09-26 06:48:32 -0700789 useStarlarkThread = true)
790 public Object getAttr(
791 Object obj, String name, Object defaultValue, Location loc, StarlarkThread thread)
cparsons7ac265d2019-04-16 15:31:17 -0700792 throws EvalException, InterruptedException {
Googlera3421e22019-09-26 06:48:32 -0700793 Object result = EvalUtils.getAttr(thread, loc, obj, name);
cparsons7ac265d2019-04-16 15:31:17 -0700794 if (result == null) {
Googlerf7e471b2019-11-11 10:10:07 -0800795 if (defaultValue != Starlark.UNBOUND) {
cparsons7ac265d2019-04-16 15:31:17 -0700796 return defaultValue;
797 }
Googlera3421e22019-09-26 06:48:32 -0700798 throw EvalUtils.getMissingFieldException(obj, name, loc, thread.getSemantics(), "attribute");
cparsons7ac265d2019-04-16 15:31:17 -0700799 }
800 return result;
801 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100802
cparsons7ac265d2019-04-16 15:31:17 -0700803 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700804 name = "dir",
cparsons507b00f2018-09-12 11:59:07 -0700805 doc =
806 "Returns a list of strings: the names of the attributes and "
807 + "methods of the parameter object.",
cparsons7ac265d2019-04-16 15:31:17 -0700808 parameters = {
cparsons6bd9a7b2019-04-25 15:39:40 -0700809 @Param(
810 name = "x",
811 doc = "The object to check.",
812 // TODO(cparsons): This parameter should be positional-only.
813 legacyNamed = true,
814 noneable = true)
cparsons7ac265d2019-04-16 15:31:17 -0700815 },
cparsons507b00f2018-09-12 11:59:07 -0700816 useLocation = true,
Googlera3421e22019-09-26 06:48:32 -0700817 useStarlarkThread = true)
Googler942e1c42019-11-12 13:11:44 -0800818 public StarlarkList<?> dir(Object object, Location loc, StarlarkThread thread)
Googlera3421e22019-09-26 06:48:32 -0700819 throws EvalException {
cparsons7ac265d2019-04-16 15:31:17 -0700820 // Order the fields alphabetically.
821 Set<String> fields = new TreeSet<>();
822 if (object instanceof ClassObject) {
823 fields.addAll(((ClassObject) object).getFieldNames());
824 }
Googlera3421e22019-09-26 06:48:32 -0700825 fields.addAll(CallUtils.getMethodNames(thread.getSemantics(), object.getClass()));
Googler942e1c42019-11-12 13:11:44 -0800826 return StarlarkList.copyOf(thread, fields);
cparsons7ac265d2019-04-16 15:31:17 -0700827 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100828
cparsons7ac265d2019-04-16 15:31:17 -0700829 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700830 name = "fail",
831 doc =
832 "Raises an error that cannot be intercepted. It can be used anywhere, "
833 + "both in the loading phase and in the analysis phase.",
cparsons507b00f2018-09-12 11:59:07 -0700834 parameters = {
835 @Param(
836 name = "msg",
837 type = Object.class,
cparsons7ac265d2019-04-16 15:31:17 -0700838 doc = "Error to display for the user. The object is converted to a string.",
839 defaultValue = "None",
cparsons6bd9a7b2019-04-25 15:39:40 -0700840 named = true,
cparsons7ac265d2019-04-16 15:31:17 -0700841 noneable = true),
cparsons507b00f2018-09-12 11:59:07 -0700842 @Param(
843 name = "attr",
844 type = String.class,
845 noneable = true,
846 defaultValue = "None",
847 doc =
848 "The name of the attribute that caused the error. This is used only for "
cparsons7ac265d2019-04-16 15:31:17 -0700849 + "error reporting.",
cparsons6bd9a7b2019-04-25 15:39:40 -0700850 named = true)
cparsons507b00f2018-09-12 11:59:07 -0700851 },
852 useLocation = true)
Googler641bdf72019-11-12 10:32:26 -0800853 public NoneType fail(Object msg, Object attr, Location loc) throws EvalException {
cparsons7ac265d2019-04-16 15:31:17 -0700854 String str = Printer.str(msg);
Googler641bdf72019-11-12 10:32:26 -0800855 if (attr != Starlark.NONE) {
cparsons7ac265d2019-04-16 15:31:17 -0700856 str = String.format("attribute %s: %s", attr, str);
857 }
858 throw new EvalException(loc, str);
859 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100860
cparsons7ac265d2019-04-16 15:31:17 -0700861 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700862 name = "print",
cparsons507b00f2018-09-12 11:59:07 -0700863 doc =
864 "Prints <code>args</code> as debug output. It will be prefixed with the string <code>"
865 + "\"DEBUG\"</code> and the location (file and line number) of this call. The "
866 + "exact way in which the arguments are converted to strings is unspecified and may "
867 + "change at any time. In particular, it may be different from (and more detailed "
868 + "than) the formatting done by <a href='#str'><code>str()</code></a> and <a "
869 + "href='#repr'><code>repr()</code></a>."
870 + "<p>Using <code>print</code> in production code is discouraged due to the spam it "
871 + "creates for users. For deprecations, prefer a hard error using <a href=\"#fail\">"
872 + "<code>fail()</code></a> whenever possible.",
873 parameters = {
874 @Param(
875 name = "sep",
876 type = String.class,
877 defaultValue = "\" \"",
878 named = true,
879 positional = false,
880 doc = "The separator string between the objects, default is space (\" \").")
881 },
882 // NB: as compared to Python3, we're missing optional named-only arguments 'end' and 'file'
883 extraPositionals = @Param(name = "args", doc = "The objects to print."),
884 useLocation = true,
Googlera3421e22019-09-26 06:48:32 -0700885 useStarlarkThread = true)
Googler641bdf72019-11-12 10:32:26 -0800886 public NoneType print(String sep, SkylarkList<?> starargs, Location loc, StarlarkThread thread)
cparsons7ac265d2019-04-16 15:31:17 -0700887 throws EvalException {
cparsonsa7c0afb2019-08-30 08:14:51 -0700888 try {
889 String msg = starargs.stream().map(Printer::debugPrint).collect(joining(sep));
890 // As part of the integration test "skylark_flag_test.sh", if the
891 // "--internal_skylark_flag_test_canary" flag is enabled, append an extra marker string to
892 // the output.
Googlera3421e22019-09-26 06:48:32 -0700893 if (thread.getSemantics().internalSkylarkFlagTestCanary()) {
cparsonsa7c0afb2019-08-30 08:14:51 -0700894 msg += "<== skylark flag test ==>";
895 }
Googlera3421e22019-09-26 06:48:32 -0700896 thread.handleEvent(Event.debug(loc, msg));
Googler641bdf72019-11-12 10:32:26 -0800897 return Starlark.NONE;
cparsonsa7c0afb2019-08-30 08:14:51 -0700898 } catch (NestedSetDepthException exception) {
899 throw new EvalException(
900 loc,
901 "depset exceeded maximum depth "
902 + exception.getDepthLimit()
903 + ". This was only discovered when attempting to flatten the depset for print(), as "
904 + "the size of depsets is unknown until flattening. "
905 + "See https://github.com/bazelbuild/bazel/issues/9180 for details and possible "
906 + "solutions.");
cparsons7ac265d2019-04-16 15:31:17 -0700907 }
cparsons7ac265d2019-04-16 15:31:17 -0700908 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100909
cparsons7ac265d2019-04-16 15:31:17 -0700910 @SkylarkCallable(
cparsonsb36470e2018-06-15 09:04:21 -0700911 name = "type",
cparsonsb36470e2018-06-15 09:04:21 -0700912 doc =
913 "Returns the type name of its argument. This is useful for debugging and "
914 + "type-checking. Examples:"
915 + "<pre class=\"language-python\">"
916 + "type(2) == \"int\"\n"
917 + "type([1]) == \"list\"\n"
918 + "type(struct(a = 2)) == \"struct\""
919 + "</pre>"
920 + "This function might change in the future. To write Python-compatible code and "
921 + "be future-proof, use it only to compare return values: "
922 + "<pre class=\"language-python\">"
923 + "if type(x) == type([]): # if x is a list"
924 + "</pre>",
cparsons7ac265d2019-04-16 15:31:17 -0700925 parameters = {
926 @Param(
927 name = "x",
928 doc = "The object to check type of.",
cparsons6bd9a7b2019-04-25 15:39:40 -0700929 // TODO(cparsons): This parameter should be positional-only.
cparsons7ac265d2019-04-16 15:31:17 -0700930 legacyNamed = true,
931 noneable = true)
932 })
933 public String type(Object object) {
934 // There is no 'type' type in Skylark, so we return a string with the type name.
935 return EvalUtils.getDataTypeName(object, false);
936 }
cparsonsb36470e2018-06-15 09:04:21 -0700937
cparsons7ac265d2019-04-16 15:31:17 -0700938 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -0700939 name = "depset",
cparsons507b00f2018-09-12 11:59:07 -0700940 doc =
941 "Creates a <a href=\"depset.html\">depset</a>. The <code>direct</code> parameter is a "
942 + "list of direct elements of the depset, and <code>transitive</code> parameter is "
943 + "a list of depsets whose elements become indirect elements of the created depset. "
944 + "The order in which elements are returned when the depset is converted to a list "
945 + "is specified by the <code>order</code> parameter. "
946 + "See the <a href=\"../depsets.md\">Depsets overview</a> for more information. "
947 + "<p> All elements (direct and indirect) of a depset must be of the same type. "
948 + "<p> The order of the created depset should be <i>compatible</i> with the order of "
949 + "its <code>transitive</code> depsets. <code>\"default\"</code> order is compatible "
950 + "with any other order, all other orders are only compatible with themselves."
951 + "<p> Note on backward/forward compatibility. This function currently accepts a "
952 + "positional <code>items</code> parameter. It is deprecated and will be removed "
953 + "in the future, and after its removal <code>direct</code> will become a sole "
954 + "positional parameter of the <code>depset</code> function. Thus, both of the "
955 + "following calls are equivalent and future-proof:<br>"
956 + "<pre class=language-python>"
957 + "depset(['a', 'b'], transitive = [...])\n"
958 + "depset(direct = ['a', 'b'], transitive = [...])\n"
959 + "</pre>",
960 parameters = {
961 @Param(
cparsonsf0cf2b42019-08-15 10:56:40 -0700962 name = "x",
cparsons507b00f2018-09-12 11:59:07 -0700963 type = Object.class,
cparsonsf0cf2b42019-08-15 10:56:40 -0700964 defaultValue = "None",
965 positional = true,
966 named = false,
967 noneable = true,
cparsons507b00f2018-09-12 11:59:07 -0700968 doc =
cparsonsf0cf2b42019-08-15 10:56:40 -0700969 "A positional parameter distinct from other parameters for legacy support. "
970 + "<p>If <code>--incompatible_disable_depset_inputs</code> is false, this "
971 + "parameter serves as the value of <code>items</code>.</p> "
972 + "<p>If <code>--incompatible_disable_depset_inputs</code> is true, this "
973 + "parameter serves as the value of <code>direct</code>.</p> "
974 + "<p>See the documentation for these parameters for more details."),
975 // TODO(cparsons): Make 'order' keyword-only.
cparsons507b00f2018-09-12 11:59:07 -0700976 @Param(
977 name = "order",
978 type = String.class,
979 defaultValue = "\"default\"",
980 doc =
981 "The traversal strategy for the new depset. See "
cparsons7ac265d2019-04-16 15:31:17 -0700982 + "<a href=\"depset.html\">here</a> for the possible values.",
cparsons6bd9a7b2019-04-25 15:39:40 -0700983 named = true),
cparsons507b00f2018-09-12 11:59:07 -0700984 @Param(
985 name = "direct",
cparsonsf0cf2b42019-08-15 10:56:40 -0700986 type = Object.class,
cparsons507b00f2018-09-12 11:59:07 -0700987 defaultValue = "None",
988 positional = false,
989 named = true,
990 noneable = true,
cparsonsf0cf2b42019-08-15 10:56:40 -0700991 doc = "A list of <i>direct</i> elements of a depset. "),
cparsons507b00f2018-09-12 11:59:07 -0700992 @Param(
993 name = "transitive",
994 named = true,
995 positional = false,
996 type = SkylarkList.class,
997 generic1 = SkylarkNestedSet.class,
998 noneable = true,
999 doc = "A list of depsets whose elements will become indirect elements of the depset.",
cparsonsf0cf2b42019-08-15 10:56:40 -07001000 defaultValue = "None"),
1001 @Param(
1002 name = "items",
1003 type = Object.class,
1004 defaultValue = "[]",
1005 positional = false,
1006 doc =
1007 "Deprecated: Either an iterable whose items become the direct elements of "
1008 + "the new depset, in left-to-right order, or else a depset that becomes "
1009 + "a transitive element of the new depset. In the latter case, "
1010 + "<code>transitive</code> cannot be specified.",
1011 disableWithFlag = FlagIdentifier.INCOMPATIBLE_DISABLE_DEPSET_INPUTS,
1012 valueWhenDisabled = "[]",
1013 named = true),
cparsons507b00f2018-09-12 11:59:07 -07001014 },
cparsonsf0cf2b42019-08-15 10:56:40 -07001015 useLocation = true,
1016 useStarlarkSemantics = true)
cparsons7ac265d2019-04-16 15:31:17 -07001017 public SkylarkNestedSet depset(
cparsonsf0cf2b42019-08-15 10:56:40 -07001018 Object x,
1019 String orderString,
1020 Object direct,
1021 Object transitive,
1022 Object items,
1023 Location loc,
1024 StarlarkSemantics semantics)
cparsons7ac265d2019-04-16 15:31:17 -07001025 throws EvalException {
1026 Order order;
1027 try {
1028 order = Order.parse(orderString);
1029 } catch (IllegalArgumentException ex) {
1030 throw new EvalException(loc, ex);
1031 }
cparsonsb36470e2018-06-15 09:04:21 -07001032
cparsonsf0cf2b42019-08-15 10:56:40 -07001033 if (semantics.incompatibleDisableDepsetItems()) {
Googler641bdf72019-11-12 10:32:26 -08001034 if (x != Starlark.NONE) {
1035 if (direct != Starlark.NONE) {
cparsonsf0cf2b42019-08-15 10:56:40 -07001036 throw new EvalException(
1037 loc, "parameter 'direct' cannot be specified both positionally and by keyword");
1038 }
1039 direct = x;
1040 }
1041 return depsetConstructor(direct, order, transitive, loc);
1042 } else {
Googler641bdf72019-11-12 10:32:26 -08001043 if (x != Starlark.NONE) {
cparsonsf0cf2b42019-08-15 10:56:40 -07001044 if (!isEmptySkylarkList(items)) {
1045 throw new EvalException(
1046 loc, "parameter 'items' cannot be specified both positionally and by keyword");
1047 }
1048 items = x;
1049 }
1050 return legacyDepsetConstructor(items, order, direct, transitive, loc);
1051 }
1052 }
1053
1054 private static SkylarkNestedSet depsetConstructor(
1055 Object direct, Order order, Object transitive, Location loc) throws EvalException {
1056
1057 if (direct instanceof SkylarkNestedSet) {
1058 throw new EvalException(
1059 loc,
1060 "parameter 'direct' must contain a list of elements, and may "
1061 + "no longer accept a depset. The deprecated behavior may be temporarily re-enabled "
1062 + "by setting --incompatible_disable_depset_inputs=false");
1063 }
1064
1065 SkylarkNestedSet.Builder builder = SkylarkNestedSet.builder(order, loc);
1066 for (Object directElement : listFromNoneable(direct, Object.class, "direct")) {
1067 builder.addDirect(directElement);
1068 }
1069 for (SkylarkNestedSet transitiveSet :
1070 listFromNoneable(transitive, SkylarkNestedSet.class, "transitive")) {
1071 builder.addTransitive(transitiveSet);
1072 }
1073 return builder.build();
1074 }
1075
1076 private static <T> List<T> listFromNoneable(
1077 Object listOrNone, Class<T> objectType, String paramName) throws EvalException {
Googler641bdf72019-11-12 10:32:26 -08001078 if (listOrNone != Starlark.NONE) {
cparsonsf0cf2b42019-08-15 10:56:40 -07001079 SkylarkType.checkType(listOrNone, SkylarkList.class, paramName);
1080 return ((SkylarkList<?>) listOrNone).getContents(objectType, paramName);
1081 } else {
1082 return ImmutableList.of();
1083 }
1084 }
1085
1086 private static SkylarkNestedSet legacyDepsetConstructor(
1087 Object items, Order order, Object direct, Object transitive, Location loc)
1088 throws EvalException {
1089
Googler641bdf72019-11-12 10:32:26 -08001090 if (transitive == Starlark.NONE && direct == Starlark.NONE) {
cparsons7ac265d2019-04-16 15:31:17 -07001091 // Legacy behavior.
1092 return SkylarkNestedSet.of(order, items, loc);
1093 }
cparsonsb36470e2018-06-15 09:04:21 -07001094
Googler641bdf72019-11-12 10:32:26 -08001095 if (direct != Starlark.NONE && !isEmptySkylarkList(items)) {
cparsons7ac265d2019-04-16 15:31:17 -07001096 throw new EvalException(
1097 loc, "Do not pass both 'direct' and 'items' argument to depset constructor.");
1098 }
cparsonsb36470e2018-06-15 09:04:21 -07001099
cparsons7ac265d2019-04-16 15:31:17 -07001100 // Non-legacy behavior: either 'transitive' or 'direct' were specified.
1101 Iterable<Object> directElements;
Googler641bdf72019-11-12 10:32:26 -08001102 if (direct != Starlark.NONE) {
cparsonsa0efc532019-08-30 09:42:53 -07001103 SkylarkType.checkType(direct, SkylarkList.class, "direct");
cparsons7ac265d2019-04-16 15:31:17 -07001104 directElements = ((SkylarkList<?>) direct).getContents(Object.class, "direct");
1105 } else {
1106 SkylarkType.checkType(items, SkylarkList.class, "items");
1107 directElements = ((SkylarkList<?>) items).getContents(Object.class, "items");
1108 }
cparsonsb36470e2018-06-15 09:04:21 -07001109
cparsons7ac265d2019-04-16 15:31:17 -07001110 Iterable<SkylarkNestedSet> transitiveList;
Googler641bdf72019-11-12 10:32:26 -08001111 if (transitive != Starlark.NONE) {
cparsons7ac265d2019-04-16 15:31:17 -07001112 SkylarkType.checkType(transitive, SkylarkList.class, "transitive");
1113 transitiveList =
1114 ((SkylarkList<?>) transitive).getContents(SkylarkNestedSet.class, "transitive");
1115 } else {
1116 transitiveList = ImmutableList.of();
1117 }
1118 SkylarkNestedSet.Builder builder = SkylarkNestedSet.builder(order, loc);
1119 for (Object directElement : directElements) {
1120 builder.addDirect(directElement);
1121 }
1122 for (SkylarkNestedSet transitiveSet : transitiveList) {
1123 builder.addTransitive(transitiveSet);
1124 }
1125 return builder.build();
1126 }
cparsonsb36470e2018-06-15 09:04:21 -07001127
1128 private static boolean isEmptySkylarkList(Object o) {
1129 return o instanceof SkylarkList && ((SkylarkList) o).isEmpty();
1130 }
1131
1132 /**
1133 * Returns a function-value implementing "select" (i.e. configurable attributes) in the specified
1134 * package context.
1135 */
cparsons7ac265d2019-04-16 15:31:17 -07001136 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -07001137 name = "select",
1138 doc =
1139 "<code>select()</code> is the helper function that makes a rule attribute "
1140 + "<a href=\"$BE_ROOT/common-definitions.html#configurable-attributes\">"
1141 + "configurable</a>. See "
1142 + "<a href=\"$BE_ROOT/functions.html#select\">build encyclopedia</a> for details.",
1143 parameters = {
cparsons7ac265d2019-04-16 15:31:17 -07001144 @Param(
1145 name = "x",
1146 type = SkylarkDict.class,
1147 doc = "The parameter to convert.",
cparsons6bd9a7b2019-04-25 15:39:40 -07001148 // TODO(cparsons): This parameter should be positional-only.
cparsons7ac265d2019-04-16 15:31:17 -07001149 legacyNamed = true),
cparsonsb36470e2018-06-15 09:04:21 -07001150 @Param(
1151 name = "no_match_error",
1152 type = String.class,
1153 defaultValue = "''",
cparsons7ac265d2019-04-16 15:31:17 -07001154 doc = "Optional custom error to report if no condition matches.",
cparsons6bd9a7b2019-04-25 15:39:40 -07001155 named = true)
cparsons507b00f2018-09-12 11:59:07 -07001156 },
1157 useLocation = true)
cparsons7ac265d2019-04-16 15:31:17 -07001158 public Object select(SkylarkDict<?, ?> dict, String noMatchError, Location loc)
1159 throws EvalException {
gregcea565f082019-05-01 07:45:50 -07001160 if (dict.isEmpty()) {
1161 throw new EvalException(
1162 loc,
1163 "select({}) with an empty dictionary can never resolve because it includes no conditions "
1164 + "to match");
1165 }
cparsons7ac265d2019-04-16 15:31:17 -07001166 for (Object key : dict.keySet()) {
1167 if (!(key instanceof String)) {
1168 throw new EvalException(
1169 loc, String.format("Invalid key: %s. select keys must be label references", key));
1170 }
1171 }
1172 return SelectorList.of(new SelectorValue(dict, noMatchError));
1173 }
cparsonsb36470e2018-06-15 09:04:21 -07001174
cparsons7ac265d2019-04-16 15:31:17 -07001175 @SkylarkCallable(
cparsons507b00f2018-09-12 11:59:07 -07001176 name = "zip",
1177 doc =
1178 "Returns a <code>list</code> of <code>tuple</code>s, where the i-th tuple contains "
1179 + "the i-th element from each of the argument sequences or iterables. The list has "
1180 + "the size of the shortest input. With a single iterable argument, it returns a "
1181 + "list of 1-tuples. With no arguments, it returns an empty list. Examples:"
1182 + "<pre class=\"language-python\">"
1183 + "zip() # == []\n"
1184 + "zip([1, 2]) # == [(1,), (2,)]\n"
1185 + "zip([1, 2], [3, 4]) # == [(1, 3), (2, 4)]\n"
1186 + "zip([1, 2], [3, 4, 5]) # == [(1, 3), (2, 4)]</pre>",
1187 extraPositionals = @Param(name = "args", doc = "lists to zip."),
cparsons507b00f2018-09-12 11:59:07 -07001188 useLocation = true,
Googlera3421e22019-09-26 06:48:32 -07001189 useStarlarkThread = true)
Googler942e1c42019-11-12 13:11:44 -08001190 public StarlarkList<?> zip(SkylarkList<?> args, Location loc, StarlarkThread thread)
cparsons7ac265d2019-04-16 15:31:17 -07001191 throws EvalException {
1192 Iterator<?>[] iterators = new Iterator<?>[args.size()];
1193 for (int i = 0; i < args.size(); i++) {
Googlera3421e22019-09-26 06:48:32 -07001194 iterators[i] = EvalUtils.toIterable(args.get(i), loc, thread).iterator();
cparsons7ac265d2019-04-16 15:31:17 -07001195 }
1196 ArrayList<Tuple<?>> result = new ArrayList<>();
1197 boolean allHasNext;
1198 do {
1199 allHasNext = !args.isEmpty();
1200 List<Object> elem = Lists.newArrayListWithExpectedSize(args.size());
1201 for (Iterator<?> iterator : iterators) {
1202 if (iterator.hasNext()) {
1203 elem.add(iterator.next());
1204 } else {
1205 allHasNext = false;
Googlerc60ec8c2015-03-23 14:20:18 +00001206 }
cparsons7ac265d2019-04-16 15:31:17 -07001207 }
1208 if (allHasNext) {
1209 result.add(Tuple.copyOf(elem));
1210 }
1211 } while (allHasNext);
Googler942e1c42019-11-12 13:11:44 -08001212 return StarlarkList.wrapUnsafe(thread, result);
cparsons7ac265d2019-04-16 15:31:17 -07001213 }
Googlerc60ec8c2015-03-23 14:20:18 +00001214
Laurent Le Brun4f3b5822017-02-15 16:20:24 +00001215 /** Skylark int type. */
1216 @SkylarkModule(
cparsons507b00f2018-09-12 11:59:07 -07001217 name = "int",
1218 category = SkylarkModuleCategory.BUILTIN,
1219 doc =
1220 "A type to represent integers. It can represent any number between -2147483648 and "
1221 + "2147483647 (included). "
1222 + "Examples of int values:<br>"
1223 + "<pre class=\"language-python\">"
1224 + "153\n"
1225 + "0x2A # hexadecimal literal\n"
vladmosc4251fb2019-05-15 04:39:58 -07001226 + "0o54 # octal literal\n"
cparsons507b00f2018-09-12 11:59:07 -07001227 + "23 * 2 + 5\n"
1228 + "100 / -7\n"
1229 + "100 % -7 # -5 (unlike in some other languages)\n"
1230 + "int(\"18\")\n"
1231 + "</pre>")
Googler93316322019-11-06 09:26:19 -08001232 static final class IntModule implements SkylarkValue {} // (documentation only)
Laurent Le Brun4f3b5822017-02-15 16:20:24 +00001233
1234 /** Skylark bool type. */
1235 @SkylarkModule(
cparsons507b00f2018-09-12 11:59:07 -07001236 name = "bool",
1237 category = SkylarkModuleCategory.BUILTIN,
1238 doc =
1239 "A type to represent booleans. There are only two possible values: "
1240 + "<a href=\"globals.html#True\">True</a> and "
1241 + "<a href=\"globals.html#False\">False</a>. "
1242 + "Any value can be converted to a boolean using the "
1243 + "<a href=\"globals.html#bool\">bool</a> function.")
Googler93316322019-11-06 09:26:19 -08001244 static final class BoolModule implements SkylarkValue {} // (documentation only)
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01001245}