92
92
+ " may interfere with the ordering semantics." )
93
93
@ Immutable
94
94
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 ;
96
98
private final NestedSet <?> set ;
97
99
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 ;
100
102
this .set = set ;
101
103
}
102
104
103
105
// Implementation of deprecated depset(items) constructor, where items is
104
106
// supplied positionally. See https://github.com/bazelbuild/bazel/issues/9017.
105
107
static Depset legacyOf (Order order , Object items ) throws EvalException {
106
- ElementType elemType = ElementType . EMPTY ;
108
+ Class <?> elemClass = null ; // special value for empty depset
107
109
NestedSetBuilder <Object > builder = new NestedSetBuilder <>(order );
108
110
109
111
if (items instanceof Depset ) {
110
112
Depset nestedSet = (Depset ) items ;
111
113
if (!nestedSet .isEmpty ()) {
112
- elemType = checkType ( elemType , nestedSet .elemType ) ;
114
+ elemClass = nestedSet .elemClass ;
113
115
try {
114
116
builder .addTransitive (nestedSet .set );
115
117
} catch (IllegalArgumentException e ) {
@@ -120,9 +122,9 @@ static Depset legacyOf(Order order, Object items) throws EvalException {
120
122
121
123
} else if (items instanceof Sequence ) {
122
124
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 );
126
128
builder .add (x );
127
129
}
128
130
@@ -131,7 +133,7 @@ static Depset legacyOf(Order order, Object items) throws EvalException {
131
133
"depset: got value of type '%s', want depset or sequence" , Starlark .type (items ));
132
134
}
133
135
134
- return new Depset (elemType , builder .build ());
136
+ return new Depset (elemClass , builder .build ());
135
137
}
136
138
137
139
private static void checkElement (Object x , boolean strict ) throws EvalException {
@@ -164,52 +166,70 @@ private static void checkElement(Object x, boolean strict) throws EvalException
164
166
}
165
167
}
166
168
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. */
173
170
// TODO(adonovan): enforce that we never construct a Depset with a StarlarkType
174
171
// that represents a non-Starlark type (e.g. NestedSet<PathFragment>).
175
172
// One way to do that is to disallow constructing StarlarkTypes for classes
176
173
// that would fail Starlark.valid; however remains the problem that
177
174
// Object.class means "any Starlark value" but in fact allows any Java value.
178
175
//
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.
181
179
// Options:
182
180
// - prohibit or ignore a non-EMPTY elemType when passed an empty NestedSet
183
181
// - continue to allow empty depsets to be distinguished by their nominal elemTypes for
184
182
// union purposes, but allow casting them to NestedSet<T> for arbitrary T.
185
183
// - distinguish them for both union and casting, i.e. replace set.isEmpty() with a check for the
186
184
// 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
191
198
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 );
193
201
}
194
202
195
203
/**
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
198
214
*/
199
- private static ElementType checkType (ElementType existingElemType , ElementType newElemType )
215
+ private static Class <?> checkType (@ Nullable Class <?> existingElemType , Class <?> newElemType )
200
216
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.
202
219
// 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 ) {
204
222
return newElemType ;
205
223
}
206
224
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 ));
208
227
}
209
228
210
229
/**
211
230
* 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.
213
233
*
214
234
* <p>If you do not specifically need the {@code NestedSet} and you are going to flatten it
215
235
* anyway, prefer {@link #toList} to make your intent clear.
@@ -219,6 +239,7 @@ private static ElementType checkType(ElementType existingElemType, ElementType n
219
239
* @throws TypeException if the type does not accurately describe all elements
220
240
*/
221
241
public <T > NestedSet <T > getSet (Class <T > type ) throws TypeException {
242
+ ElementType elemType = getElementType ();
222
243
if (!set .isEmpty () && !elemType .canBeCastTo (type )) {
223
244
throw new TypeException (
224
245
String .format (
@@ -243,7 +264,8 @@ public NestedSet<?> getSet() {
243
264
* instance of class {@code type}. Requires traversing the entire graph of the underlying
244
265
* NestedSet.
245
266
*
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)
247
269
* @throws TypeException if the type does not accurately describe all elements
248
270
*/
249
271
public <T > ImmutableList <T > toList (Class <T > type ) throws TypeException {
@@ -259,10 +281,17 @@ public ImmutableList<?> toList() {
259
281
}
260
282
261
283
/**
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)
266
295
*/
267
296
public static <T > NestedSet <T > cast (Object x , Class <T > type , String what ) throws EvalException {
268
297
if (!(x instanceof Depset )) {
@@ -299,7 +328,10 @@ public boolean truth() {
299
328
}
300
329
301
330
public ElementType getElementType () {
302
- return elemType ;
331
+ if (elemClass == null ) {
332
+ return ElementType .EMPTY ;
333
+ }
334
+ return ElementType .of (elemClass );
303
335
}
304
336
305
337
@ Override
@@ -355,7 +387,7 @@ static Depset fromDirectAndTransitive(
355
387
Order order , List <Object > direct , List <Depset > transitive , boolean strict )
356
388
throws EvalException {
357
389
NestedSetBuilder <Object > builder = new NestedSetBuilder <>(order );
358
- ElementType type = ElementType . EMPTY ;
390
+ Class <?> type = null ;
359
391
360
392
// Check direct elements' type is equal to elements already added.
361
393
for (Object x : direct ) {
@@ -372,15 +404,15 @@ static Depset fromDirectAndTransitive(
372
404
// See b/144992997 or github.com/bazelbuild/bazel/issues/10289.
373
405
checkElement (x , /*strict=*/ strict );
374
406
375
- ElementType xt = ElementType .of (x .getClass ());
407
+ Class <?> xt = ElementType .getTypeClass (x .getClass ());
376
408
type = checkType (type , xt );
377
409
}
378
410
builder .addAll (direct );
379
411
380
412
// Add transitive sets, checking that type is equal to elements already added.
381
413
for (Depset x : transitive ) {
382
414
if (!x .isEmpty ()) {
383
- type = checkType (type , x .getElementType () );
415
+ type = checkType (type , x .elemClass );
384
416
if (!order .isCompatible (x .getOrder ())) {
385
417
throw Starlark .errorf (
386
418
"Order '%s' is incompatible with order '%s'" ,
@@ -475,7 +507,7 @@ private static Class<?> getTypeClass(Class<?> cls) {
475
507
//
476
508
// Fails if cls is neither Object.class nor a valid Starlark value class.
477
509
// 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
479
511
// passed a supertype of a Starlark class that is not itself a valid Starlark
480
512
// value class. As a special case, Object.class is permitted,
481
513
// and represents "any value".
0 commit comments