Skip to content

Commit c162166

Browse files
comiuscopybara-github
authored andcommitted
Change ElementType to Class<?> in Depset
The ElementType is causing a regression, because it's not interned. The cl changes it to Class<?> of the element, which doesn't need interning. The semantics is preserved. This removes about 5% retained heap regression that would be caused by Starlarkifying ProtoInfo. PiperOrigin-RevId: 504476415 Change-Id: I43c7f61c07b7cfd7d630a180e8cc1f00927707b2
1 parent 0596e1b commit c162166

File tree

1 file changed

+71
-39
lines changed
  • src/main/java/com/google/devtools/build/lib/collect/nestedset

1 file changed

+71
-39
lines changed

src/main/java/com/google/devtools/build/lib/collect/nestedset/Depset.java

+71-39
Original file line numberDiff line numberDiff line change
@@ -92,24 +92,26 @@
9292
+ " may interfere with the ordering semantics.")
9393
@Immutable
9494
public final class Depset implements StarlarkValue, Debug.ValueWithDebugAttributes {
95-
private final ElementType elemType;
95+
// The value of elemClass is set to getTypeClass(actualElemClass)
96+
// null is used for empty Depset-s
97+
@Nullable private final Class<?> elemClass;
9698
private final NestedSet<?> set;
9799

98-
private Depset(ElementType elemType, NestedSet<?> set) {
99-
this.elemType = Preconditions.checkNotNull(elemType, "element type cannot be null");
100+
private Depset(@Nullable Class<?> elemClass, NestedSet<?> set) {
101+
this.elemClass = elemClass;
100102
this.set = set;
101103
}
102104

103105
// Implementation of deprecated depset(items) constructor, where items is
104106
// supplied positionally. See https://github.com/bazelbuild/bazel/issues/9017.
105107
static Depset legacyOf(Order order, Object items) throws EvalException {
106-
ElementType elemType = ElementType.EMPTY;
108+
Class<?> elemClass = null; // special value for empty depset
107109
NestedSetBuilder<Object> builder = new NestedSetBuilder<>(order);
108110

109111
if (items instanceof Depset) {
110112
Depset nestedSet = (Depset) items;
111113
if (!nestedSet.isEmpty()) {
112-
elemType = checkType(elemType, nestedSet.elemType);
114+
elemClass = nestedSet.elemClass;
113115
try {
114116
builder.addTransitive(nestedSet.set);
115117
} catch (IllegalArgumentException e) {
@@ -120,9 +122,9 @@ static Depset legacyOf(Order order, Object items) throws EvalException {
120122

121123
} else if (items instanceof Sequence) {
122124
for (Object x : (Sequence) items) {
123-
checkElement(x, /*strict=*/ true);
124-
ElementType xt = ElementType.of(x.getClass());
125-
elemType = checkType(elemType, xt);
125+
checkElement(x, /* strict= */ true);
126+
Class<?> xt = ElementType.getTypeClass(x.getClass());
127+
elemClass = checkType(elemClass, xt);
126128
builder.add(x);
127129
}
128130

@@ -131,7 +133,7 @@ static Depset legacyOf(Order order, Object items) throws EvalException {
131133
"depset: got value of type '%s', want depset or sequence", Starlark.type(items));
132134
}
133135

134-
return new Depset(elemType, builder.build());
136+
return new Depset(elemClass, builder.build());
135137
}
136138

137139
private static void checkElement(Object x, boolean strict) throws EvalException {
@@ -164,52 +166,70 @@ private static void checkElement(Object x, boolean strict) throws EvalException
164166
}
165167
}
166168

167-
/**
168-
* Returns a Depset that wraps the specified NestedSet.
169-
*
170-
* <p>This operation is type-safe only if the specified element type is appropriate for every
171-
* element of the set.
172-
*/
169+
/** Returns a Depset that wraps the specified NestedSet. */
173170
// TODO(adonovan): enforce that we never construct a Depset with a StarlarkType
174171
// that represents a non-Starlark type (e.g. NestedSet<PathFragment>).
175172
// One way to do that is to disallow constructing StarlarkTypes for classes
176173
// that would fail Starlark.valid; however remains the problem that
177174
// Object.class means "any Starlark value" but in fact allows any Java value.
178175
//
179-
// TODO(adonovan): it is possible to create an empty depset with a elemType other than EMPTY.
180-
// The union operation will fail if it's combined with another depset of incompatible elemType.
176+
// TODO(adonovan): it is possible to create an empty depset with a elemType (elemClass) other
177+
// than EMPTY (null). The union operation will fail if it's combined with another depset of
178+
// incompatible elemType.
181179
// Options:
182180
// - prohibit or ignore a non-EMPTY elemType when passed an empty NestedSet
183181
// - continue to allow empty depsets to be distinguished by their nominal elemTypes for
184182
// union purposes, but allow casting them to NestedSet<T> for arbitrary T.
185183
// - distinguish them for both union and casting, i.e. replace set.isEmpty() with a check for the
186184
// empty type.
187-
//
188-
// TODO(adonovan): if we replaced ElementType by Class, we could enforce consistency between the
189-
// two arguments: of(Class<T> elemType, NestedSet<T> set). We could also avoid the allocations
190-
// done by ElementType.of().
185+
public static <T> Depset of(Class<T> elemClass, NestedSet<T> set) {
186+
return new Depset(ElementType.getTypeClass(elemClass), set);
187+
}
188+
189+
/**
190+
* Returns a Depset that wraps the specified NestedSet.
191+
*
192+
* <p>This operation is type-safe only if the specified element type is appropriate for every
193+
* element of the set.
194+
*
195+
* <p>@Deprecated Use {@code #of} with the {@code elemClass} instead.
196+
*/
197+
@Deprecated
191198
public static <T> Depset of(ElementType elemType, NestedSet<T> set) {
192-
return new Depset(elemType, set);
199+
Preconditions.checkNotNull(elemType, "element type cannot be null");
200+
return new Depset(elemType.cls, set);
193201
}
194202

195203
/**
196-
* Checks that an item type is allowed in a given set type, and returns the type of a new depset
197-
* with that item inserted.
204+
* Checks that an element with {@code newElemType} is permitted in a set of {@code
205+
* existingElemType}.
206+
*
207+
* <p>{@code existingElemType} may be null, corresponding to a set that does not yet have any
208+
* elements.
209+
*
210+
* <p>Both Class-es should be returned by getTypeClass(cls).
211+
*
212+
* @return the (non-null) element type for a new set that adds the element
213+
* @throws EvalException if the new type is not permitted
198214
*/
199-
private static ElementType checkType(ElementType existingElemType, ElementType newElemType)
215+
private static Class<?> checkType(@Nullable Class<?> existingElemType, Class<?> newElemType)
200216
throws EvalException {
201-
// An initially empty depset takes its type from the first element added.
217+
// An initially empty depset (existingElemType == null) takes its type from the first element
218+
// added.
202219
// Otherwise, the types of the item and depset must match exactly.
203-
if (existingElemType.equals(ElementType.EMPTY) || existingElemType.equals(newElemType)) {
220+
Preconditions.checkNotNull(newElemType);
221+
if (existingElemType == null || existingElemType == newElemType) {
204222
return newElemType;
205223
}
206224
throw Starlark.errorf(
207-
"cannot add an item of type '%s' to a depset of '%s'", newElemType, existingElemType);
225+
"cannot add an item of type '%s' to a depset of '%s'",
226+
ElementType.of(newElemType), ElementType.of(existingElemType));
208227
}
209228

210229
/**
211230
* Returns the embedded {@link NestedSet}, first asserting that its elements are instances of the
212-
* given class. Only the top-level class is verified.
231+
* given class, which must be a valid Starlark type (or Object.class). Only the top-level class is
232+
* verified.
213233
*
214234
* <p>If you do not specifically need the {@code NestedSet} and you are going to flatten it
215235
* anyway, prefer {@link #toList} to make your intent clear.
@@ -219,6 +239,7 @@ private static ElementType checkType(ElementType existingElemType, ElementType n
219239
* @throws TypeException if the type does not accurately describe all elements
220240
*/
221241
public <T> NestedSet<T> getSet(Class<T> type) throws TypeException {
242+
ElementType elemType = getElementType();
222243
if (!set.isEmpty() && !elemType.canBeCastTo(type)) {
223244
throw new TypeException(
224245
String.format(
@@ -243,7 +264,8 @@ public NestedSet<?> getSet() {
243264
* instance of class {@code type}. Requires traversing the entire graph of the underlying
244265
* NestedSet.
245266
*
246-
* @param type a {@link Class} representing the expected type of the elements
267+
* @param type a {@link Class} representing the expected type of the elements, which must be a
268+
* valid Starlark type (or Object.class)
247269
* @throws TypeException if the type does not accurately describe all elements
248270
*/
249271
public <T> ImmutableList<T> toList(Class<T> type) throws TypeException {
@@ -259,10 +281,17 @@ public ImmutableList<?> toList() {
259281
}
260282

261283
/**
262-
* Casts a non-null Starlark value {@code x} to a {@code Depset} and returns its {@code
263-
* NestedSet<T>}, after checking that all elements are instances of {@code type}. On error, it
264-
* throws an EvalException whose message includes {@code what}, ideally a string literal, as a
265-
* description of the role of {@code x}.
284+
* Casts a non-null Starlark value {@code x} to a {@code Depset} and returns its underlying {@code
285+
* NestedSet<T>} (where {@code type} reifies {@code T}).
286+
*
287+
* <p>It may be assumed that all elements of the depset are of type {@code T}, but no actual
288+
* iteration takes place.
289+
*
290+
* <p>If {@code x} is not a depset or does not have the right element type, this throws an {@code
291+
* EvalException} whose message includes {@code what}, ideally a string literal, as a description
292+
* of the role of {@code x}.
293+
*
294+
* @throws IllegalArgumentException if {@code type} is not a valid Starlark type (or Object.class)
266295
*/
267296
public static <T> NestedSet<T> cast(Object x, Class<T> type, String what) throws EvalException {
268297
if (!(x instanceof Depset)) {
@@ -299,7 +328,10 @@ public boolean truth() {
299328
}
300329

301330
public ElementType getElementType() {
302-
return elemType;
331+
if (elemClass == null) {
332+
return ElementType.EMPTY;
333+
}
334+
return ElementType.of(elemClass);
303335
}
304336

305337
@Override
@@ -355,7 +387,7 @@ static Depset fromDirectAndTransitive(
355387
Order order, List<Object> direct, List<Depset> transitive, boolean strict)
356388
throws EvalException {
357389
NestedSetBuilder<Object> builder = new NestedSetBuilder<>(order);
358-
ElementType type = ElementType.EMPTY;
390+
Class<?> type = null;
359391

360392
// Check direct elements' type is equal to elements already added.
361393
for (Object x : direct) {
@@ -372,15 +404,15 @@ static Depset fromDirectAndTransitive(
372404
// See b/144992997 or github.com/bazelbuild/bazel/issues/10289.
373405
checkElement(x, /*strict=*/ strict);
374406

375-
ElementType xt = ElementType.of(x.getClass());
407+
Class<?> xt = ElementType.getTypeClass(x.getClass());
376408
type = checkType(type, xt);
377409
}
378410
builder.addAll(direct);
379411

380412
// Add transitive sets, checking that type is equal to elements already added.
381413
for (Depset x : transitive) {
382414
if (!x.isEmpty()) {
383-
type = checkType(type, x.getElementType());
415+
type = checkType(type, x.elemClass);
384416
if (!order.isCompatible(x.getOrder())) {
385417
throw Starlark.errorf(
386418
"Order '%s' is incompatible with order '%s'",
@@ -475,7 +507,7 @@ private static Class<?> getTypeClass(Class<?> cls) {
475507
//
476508
// Fails if cls is neither Object.class nor a valid Starlark value class.
477509
// One might expect that if a ElementType canBeCastTo Integer, then it can
478-
// also be cast to Number, but this is not the case: getTypeClass fails if
510+
// also be cast to Number, but this is not the case: getTypeClass throws IAE if
479511
// passed a supertype of a Starlark class that is not itself a valid Starlark
480512
// value class. As a special case, Object.class is permitted,
481513
// and represents "any value".

0 commit comments

Comments
 (0)