Introduce --incompatible_depset_is_not_iterable
RELNOTES:
Iterating on a `depset` object is deprecated. If you need an iterable,
call the `.to_list()` method first.
PiperOrigin-RevId: 159672887
diff --git a/site/docs/skylark/concepts.md b/site/docs/skylark/concepts.md
index 7c68d74..0ce1c91 100644
--- a/site/docs/skylark/concepts.md
+++ b/site/docs/skylark/concepts.md
@@ -234,6 +234,26 @@
* Default: `false`
+### Depset is no longer iterable
+
+When the flag is set to true, `depset` objects are not treated as iterable. If
+you need an iterable, call the `.to_list()` method. This affects `for` loops and
+many functions, e.g. `list`, `tuple`, `min`, `max`, `sorted`, `all`, `any`. The
+goal of this change is to avoid accidental iteration on `depset`, which can be
+expensive.
+
+``` python
+deps = depset()
+[x.path for x in deps] # deprecated
+[x.path for x in deps.to_list()] # recommended
+
+sorted(deps) # deprecated
+sorted(deps.to_list()) # recommended
+```
+
+* Flag: `--incompatible_depset_is_not_iterable`
+* Default: `false`
+
## Upcoming changes
The following items are upcoming changes.
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkFileType.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkFileType.java
index a32c6bc..84adc69 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkFileType.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkFileType.java
@@ -24,7 +24,6 @@
import com.google.devtools.build.lib.util.FileType;
import com.google.devtools.build.lib.util.FileTypeSet;
-
/** A wrapper class for FileType and FileTypeSet functionality in Skylark. */
@SkylarkModule(
name = "FileType",
@@ -62,8 +61,7 @@
public ImmutableList<Artifact> filter(Object filesUnchecked) throws EvalException {
return ImmutableList.copyOf(
FileType.filter(
- (Iterable<Artifact>) EvalUtils.toIterableStrict(filesUnchecked, null),
- fileType));
+ (Iterable<Artifact>) EvalUtils.toIterableStrict(filesUnchecked, null, null), fileType));
}
@VisibleForTesting
diff --git a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java
index a25a460..5908834 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/SkylarkRuleImplementationFunctions.java
@@ -669,7 +669,6 @@
}
};
-
/**
* Ensures the given {@link Map} has keys that have {@link Label} type and values that have either
* {@link Iterable} or {@link SkylarkNestedSet} type, and raises {@link EvalException} otherwise.
@@ -678,8 +677,7 @@
// TODO(bazel-team): find a better way to typecheck this argument.
@SuppressWarnings("unchecked")
private static Map<Label, Iterable<Artifact>> checkLabelDict(
- Map<?, ?> labelDict, Location loc)
- throws EvalException {
+ Map<?, ?> labelDict, Location loc, Environment env) throws EvalException {
Map<Label, Iterable<Artifact>> convertedMap = new HashMap<>();
for (Map.Entry<?, ?> entry : labelDict.entrySet()) {
Object key = entry.getKey();
@@ -691,7 +689,7 @@
Object val = entry.getValue();
Iterable<?> valIter;
try {
- valIter = EvalUtils.toIterableStrict(val, loc);
+ valIter = EvalUtils.toIterableStrict(val, loc, env);
} catch (EvalException ex) {
// EvalException is thrown only if the type is wrong.
throw new EvalException(
@@ -813,7 +811,7 @@
throws ConversionException, EvalException {
ctx.checkMutable("resolve_command");
Label ruleLabel = ctx.getLabel();
- Map<Label, Iterable<Artifact>> labelDict = checkLabelDict(labelDictUnchecked, loc);
+ Map<Label, Iterable<Artifact>> labelDict = checkLabelDict(labelDictUnchecked, loc, env);
// The best way to fix this probably is to convert CommandHelper to Skylark.
CommandHelper helper =
new CommandHelper(
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/AbstractComprehension.java b/src/main/java/com/google/devtools/build/lib/syntax/AbstractComprehension.java
index 3ec6cd5..deb939e 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/AbstractComprehension.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/AbstractComprehension.java
@@ -113,7 +113,7 @@
throws EvalException, InterruptedException {
Object listValueObject = list.eval(env);
Location loc = collector.getLocation();
- Iterable<?> listValue = EvalUtils.toIterable(listValueObject, loc);
+ Iterable<?> listValue = EvalUtils.toIterable(listValueObject, loc, env);
EvalUtils.lock(listValueObject, loc);
try {
for (Object listElement : listValue) {
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
index 34ab3f3..1e3b134 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java
@@ -29,6 +29,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import javax.annotation.Nullable;
/**
* Utilities used by the evaluator.
@@ -300,7 +301,8 @@
}
}
- public static Collection<?> toCollection(Object o, Location loc) throws EvalException {
+ public static Collection<?> toCollection(Object o, Location loc, @Nullable Environment env)
+ throws EvalException {
if (o instanceof Collection) {
return (Collection<?>) o;
} else if (o instanceof SkylarkList) {
@@ -323,25 +325,36 @@
throw new EvalException(loc, e);
}
} else if (o instanceof SkylarkNestedSet) {
- return ((SkylarkNestedSet) o).toCollection();
+ return nestedSetToCollection((SkylarkNestedSet) o, loc, env);
} else {
throw new EvalException(loc,
"type '" + getDataTypeName(o) + "' is not a collection");
}
}
- public static Iterable<?> toIterable(Object o, Location loc) throws EvalException {
+ private static Collection<?> nestedSetToCollection(
+ SkylarkNestedSet set, Location loc, @Nullable Environment env) throws EvalException {
+ if (env != null && env.getSemantics().incompatibleDepsetIsNotIterable) {
+ throw new EvalException(
+ loc,
+ "type 'depset' is not iterable. Use the `to_list()` method to get a list. "
+ + "Use --incompatible_depset_is_not_iterable to temporarily disable this check.");
+ }
+ return set.toCollection();
+ }
+
+ public static Iterable<?> toIterable(Object o, Location loc, @Nullable Environment env)
+ throws EvalException {
if (o instanceof String) {
// This is not as efficient as special casing String in for and dict and list comprehension
// statements. However this is a more unified way.
return split((String) o);
} else if (o instanceof SkylarkNestedSet) {
- // TODO(bazel-team): Add a deprecation warning: don't implicitly flatten depsets.
- return ((SkylarkNestedSet) o).toCollection();
+ return nestedSetToCollection((SkylarkNestedSet) o, loc, env);
} else if (o instanceof Iterable) {
return (Iterable<?>) o;
} else if (o instanceof Map) {
- return toCollection(o, loc);
+ return toCollection(o, loc, env);
} else {
throw new EvalException(loc,
"type '" + getDataTypeName(o) + "' is not iterable");
@@ -358,18 +371,17 @@
*
* @throws EvalException if {@code o} is not an iterable or set
* @deprecated avoid writing APIs that implicitly treat depsets as iterables. It encourages
- * unnecessary flattening of depsets.
- *
- * <p>TODO(bazel-team): Remove this if/when implicit iteration over {@code SkylarkNestedSet} is no
- * longer supported.
+ * unnecessary flattening of depsets.
+ * <p>TODO(bazel-team): Remove this if/when implicit iteration over {@code SkylarkNestedSet}
+ * is no longer supported.
*/
@Deprecated
- public static Iterable<?> toIterableStrict(Object o, Location loc) throws EvalException {
+ public static Iterable<?> toIterableStrict(Object o, Location loc, @Nullable Environment env)
+ throws EvalException {
if (o instanceof Iterable) {
return (Iterable<?>) o;
} else if (o instanceof SkylarkNestedSet) {
- // TODO(bazel-team): Add a deprecation warning: don't implicitly flatten depsets.
- return ((SkylarkNestedSet) o).toCollection();
+ return nestedSetToCollection((SkylarkNestedSet) o, loc, env);
} else {
throw new EvalException(loc,
"expected Iterable or depset, but got '" + getDataTypeName(o) + "' (strings and maps "
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ForStatement.java b/src/main/java/com/google/devtools/build/lib/syntax/ForStatement.java
index 34d0c30..a43d289 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ForStatement.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ForStatement.java
@@ -61,7 +61,7 @@
@Override
void doExec(Environment env) throws EvalException, InterruptedException {
Object o = collection.eval(env);
- Iterable<?> col = EvalUtils.toIterable(o, getLocation());
+ Iterable<?> col = EvalUtils.toIterable(o, getLocation(), env);
EvalUtils.lock(o, getLocation());
try {
for (Object it : col) {
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/LValue.java b/src/main/java/com/google/devtools/build/lib/syntax/LValue.java
index 52753cc..72c9ac2 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/LValue.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/LValue.java
@@ -115,7 +115,7 @@
if (lhs instanceof ListLiteral) {
ListLiteral variables = (ListLiteral) lhs;
- Collection<?> rvalue = EvalUtils.toCollection(result, loc);
+ Collection<?> rvalue = EvalUtils.toCollection(result, loc, env);
int len = variables.getElements().size();
if (len != rvalue.size()) {
throw new EvalException(loc, String.format(
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
index d84fbc4..afa4afd 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/MethodLibrary.java
@@ -949,13 +949,18 @@
defaultValue = "{}",
doc = "Dictionary of arguments."
),
- useLocation = true
+ useLocation = true,
+ useEnvironment = true
)
private static final BuiltinFunction format =
new BuiltinFunction("format") {
@SuppressWarnings("unused")
public String invoke(
- String self, SkylarkList<Object> args, SkylarkDict<?, ?> kwargs, Location loc)
+ String self,
+ SkylarkList<Object> args,
+ SkylarkDict<?, ?> kwargs,
+ Location loc,
+ Environment env)
throws EvalException {
return new FormatParser(loc)
.format(
@@ -992,14 +997,16 @@
+ "If only one argument is provided, it must be a non-empty iterable.",
extraPositionals =
@Param(name = "args", type = SkylarkList.class, doc = "The elements to be checked."),
- useLocation = true
+ useLocation = true,
+ useEnvironment = true
)
private static final BuiltinFunction min =
new BuiltinFunction("min") {
@SuppressWarnings("unused") // Accessed via Reflection.
- public Object invoke(SkylarkList<?> args, Location loc) throws EvalException {
+ public Object invoke(SkylarkList<?> args, Location loc, Environment env)
+ throws EvalException {
try {
- return findExtreme(args, EvalUtils.SKYLARK_COMPARATOR.reverse(), loc);
+ return findExtreme(args, EvalUtils.SKYLARK_COMPARATOR.reverse(), loc, env);
} catch (ComparisonException e) {
throw new EvalException(loc, e);
}
@@ -1014,29 +1021,30 @@
+ "If only one argument is provided, it must be a non-empty iterable.",
extraPositionals =
@Param(name = "args", type = SkylarkList.class, doc = "The elements to be checked."),
- useLocation = true
+ useLocation = true,
+ useEnvironment = true
)
private static final BuiltinFunction max =
new BuiltinFunction("max") {
@SuppressWarnings("unused") // Accessed via Reflection.
- public Object invoke(SkylarkList<?> args, Location loc) throws EvalException {
+ public Object invoke(SkylarkList<?> args, Location loc, Environment env)
+ throws EvalException {
try {
- return findExtreme(args, EvalUtils.SKYLARK_COMPARATOR, loc);
+ return findExtreme(args, EvalUtils.SKYLARK_COMPARATOR, loc, env);
} catch (ComparisonException e) {
throw new EvalException(loc, e);
}
}
};
- /**
- * Returns the maximum element from this list, as determined by maxOrdering.
- */
- private static Object findExtreme(SkylarkList<?> args, Ordering<Object> maxOrdering, Location loc)
+ /** Returns the maximum element from this list, as determined by maxOrdering. */
+ private static Object findExtreme(
+ SkylarkList<?> args, Ordering<Object> maxOrdering, Location loc, Environment env)
throws EvalException {
// Args can either be a list of items to compare, or a singleton list whose element is an
// iterable of items to compare. In either case, there must be at least one item to compare.
try {
- Iterable<?> items = (args.size() == 1) ? EvalUtils.toIterable(args.get(0), loc) : args;
+ Iterable<?> items = (args.size() == 1) ? EvalUtils.toIterable(args.get(0), loc, env) : args;
return maxOrdering.max(items);
} catch (NoSuchElementException ex) {
throw new EvalException(loc, "expected at least one item");
@@ -1050,13 +1058,15 @@
parameters = {
@Param(name = "elements", type = Object.class, doc = "A string or a collection of elements.")
},
- useLocation = true
+ useLocation = true,
+ useEnvironment = true
)
private static final BuiltinFunction all =
new BuiltinFunction("all") {
@SuppressWarnings("unused") // Accessed via Reflection.
- public Boolean invoke(Object collection, Location loc) throws EvalException {
- return !hasElementWithBooleanValue(collection, false, loc);
+ public Boolean invoke(Object collection, Location loc, Environment env)
+ throws EvalException {
+ return !hasElementWithBooleanValue(collection, false, loc, env);
}
};
@@ -1067,19 +1077,21 @@
parameters = {
@Param(name = "elements", type = Object.class, doc = "A string or a collection of elements.")
},
- useLocation = true
+ useLocation = true,
+ useEnvironment = true
)
private static final BuiltinFunction any =
new BuiltinFunction("any") {
@SuppressWarnings("unused") // Accessed via Reflection.
- public Boolean invoke(Object collection, Location loc) throws EvalException {
- return hasElementWithBooleanValue(collection, true, loc);
+ public Boolean invoke(Object collection, Location loc, Environment env)
+ throws EvalException {
+ return hasElementWithBooleanValue(collection, true, loc, env);
}
};
- private static boolean hasElementWithBooleanValue(Object collection, boolean value, Location loc)
- throws EvalException {
- Iterable<?> iterable = EvalUtils.toIterable(collection, loc);
+ private static boolean hasElementWithBooleanValue(
+ Object collection, boolean value, Location loc, Environment env) throws EvalException {
+ Iterable<?> iterable = EvalUtils.toIterable(collection, loc, env);
for (Object obj : iterable) {
if (EvalUtils.toBoolean(obj) == value) {
return true;
@@ -1105,7 +1117,8 @@
throws EvalException {
try {
return new MutableList(
- EvalUtils.SKYLARK_COMPARATOR.sortedCopy(EvalUtils.toCollection(self, loc)), env);
+ EvalUtils.SKYLARK_COMPARATOR.sortedCopy(EvalUtils.toCollection(self, loc, env)),
+ env);
} catch (EvalUtils.ComparisonException e) {
throw new EvalException(loc, e);
}
@@ -1140,7 +1153,7 @@
loc, "Argument to reversed() must be a sequence, not a depset.");
}
LinkedList<Object> tmpList = new LinkedList<>();
- for (Object element : EvalUtils.toIterable(sequence, loc)) {
+ for (Object element : EvalUtils.toIterable(sequence, loc, env)) {
tmpList.addFirst(element);
}
return new MutableList(tmpList, env);
@@ -1560,12 +1573,13 @@
+ "tuple((2, 3, 2)) == (2, 3, 2)\n"
+ "tuple({5: \"a\", 2: \"b\", 4: \"c\"}) == (5, 2, 4)</pre>",
parameters = {@Param(name = "x", doc = "The object to convert.")},
- useLocation = true
+ useLocation = true,
+ useEnvironment = true
)
private static final BuiltinFunction tuple =
new BuiltinFunction("tuple") {
- public Tuple<?> invoke(Object x, Location loc) throws EvalException {
- return Tuple.create(ImmutableList.copyOf(EvalUtils.toCollection(x, loc)));
+ public Tuple<?> invoke(Object x, Location loc, Environment env) throws EvalException {
+ return Tuple.create(ImmutableList.copyOf(EvalUtils.toCollection(x, loc, env)));
}
};
@@ -1584,7 +1598,7 @@
private static final BuiltinFunction list =
new BuiltinFunction("list") {
public MutableList<?> invoke(Object x, Location loc, Environment env) throws EvalException {
- return new MutableList(EvalUtils.toCollection(x, loc), env);
+ return new MutableList(EvalUtils.toCollection(x, loc, env), env);
}
};
@@ -2114,7 +2128,7 @@
throws EvalException {
Iterator<?>[] iterators = new Iterator<?>[args.size()];
for (int i = 0; i < args.size(); i++) {
- iterators[i] = EvalUtils.toIterable(args.get(i), loc).iterator();
+ iterators[i] = EvalUtils.toIterable(args.get(i), loc, env).iterator();
}
List<Tuple<?>> result = new ArrayList<>();
boolean allHasNext;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemanticsOptions.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemanticsOptions.java
index 9f92cf9..4cca541 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemanticsOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSemanticsOptions.java
@@ -118,4 +118,15 @@
+ " comprehension executes."
)
public boolean incompatibleComprehensionVariablesDoNotLeak;
+
+ @Option(
+ name = "incompatible_depset_is_not_iterable",
+ defaultValue = "false",
+ category = "incompatible changes",
+ help =
+ "If set to true, depset type is not iterable. For loops and functions expecting an "
+ + "iterable will reject depset objects. Use the `.to_list` method to explicitly "
+ + "convert to a list."
+ )
+ public boolean incompatibleDepsetIsNotIterable;
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Type.java b/src/main/java/com/google/devtools/build/lib/syntax/Type.java
index c83a72b..da2750d 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Type.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Type.java
@@ -582,7 +582,7 @@
throws ConversionException {
Iterable<?> iterable;
try {
- iterable = EvalUtils.toIterableStrict(x, null);
+ iterable = EvalUtils.toIterableStrict(x, null, null);
} catch (EvalException ex) {
throw new ConversionException(this, x, what);
}
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EvalUtilsTest.java b/src/test/java/com/google/devtools/build/lib/syntax/EvalUtilsTest.java
index edf1edf..b7e6f04 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/EvalUtilsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/EvalUtilsTest.java
@@ -47,12 +47,12 @@
@Test
public void testEmptyStringToIterable() throws Exception {
- assertThat(EvalUtils.toIterable("", null)).isEmpty();
+ assertThat(EvalUtils.toIterable("", null, null)).isEmpty();
}
@Test
public void testStringToIterable() throws Exception {
- assertThat(EvalUtils.toIterable("abc", null)).hasSize(3);
+ assertThat(EvalUtils.toIterable("abc", null, null)).hasSize(3);
}
@Test
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
index ba6fd69..a8248ea 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkEvaluationTest.java
@@ -880,6 +880,26 @@
}
@Test
+ public void testSetIsNotIterable() throws Exception {
+ new SkylarkTest("--incompatible_depset_is_not_iterable=true")
+ .testIfErrorContains("not iterable", "list(depset(['a', 'b']))")
+ .testIfErrorContains("not iterable", "max(depset([1, 2, 3]))")
+ .testIfErrorContains("not iterable", "sorted(depset(['a', 'b']))")
+ .testIfErrorContains("not iterable", "tuple(depset(['a', 'b']))")
+ .testIfErrorContains("not iterable", "[x for x in depset()]");
+ }
+
+ @Test
+ public void testSetIsIterable() throws Exception {
+ new SkylarkTest("--incompatible_depset_is_not_iterable=false")
+ .testStatement("str(list(depset(['a', 'b'])))", "[\"a\", \"b\"]")
+ .testStatement("max(depset([1, 2, 3]))", 3)
+ .testStatement("str(sorted(depset(['b', 'a'])))", "[\"a\", \"b\"]")
+ .testStatement("str(tuple(depset(['a', 'b'])))", "(\"a\", \"b\")")
+ .testStatement("str([x for x in depset()])", "[]");
+ }
+
+ @Test
public void testClassObjectCannotAccessNestedSet() throws Exception {
new SkylarkTest()
.update("mock", new MockClassObject())