aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java
blob: 524f06e4d54a256ec8453e4ed6fa6137f2a153e7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.syntax;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Interner;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.concurrent.BlazeInterners;
import com.google.devtools.build.lib.events.Location;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;

/**
 * A class representing types available in Skylark.
 *
 * <p>A SkylarkType can be one of:
 * <ul>
 * <li>a Simple type that contains exactly the objects in a given class,
 *  (including the special TOP and BOTTOM types that respectively contain
 *  all the objects (Simple type for Object.class) and no object at all
 *  (Simple type for EmptyType.class, isomorphic to Void.class).
 * <li>a Combination of a generic class (one of SET, selector)
 *  and an argument type (that itself need not be Simple).
 * <li>a Union of a finite set of types
 * <li>a FunctionType associated with a name and a returnType
 * </ul>
 *
 * <p>In a style reminiscent of Java's null, Skylark's None is in all the types
 *  as far as type inference goes, yet actually no type .contains(it).
 *
 * <p>The current implementation fails to distinguish between TOP and ANY,
 * between BOTTOM and EMPTY (VOID, ZERO, FALSE):
 * <ul>
 * <li>In type analysis, we often distinguish a notion of "the type of this object"
 *  from the notion of "what I know about the type of this object".
 *  Some languages have a Universal Base Class that contains all objects, and would be the ANY type.
 *  The Skylark runtime, written in Java, has this ANY type, Java's Object.class.
 *  But the Skylark validation engine doesn't really have a concept of an ANY class;
 *  however, it does have a concept of a yet-undermined class, the TOP class
 *  (called UNKOWN in previous code). In the future, we may have to distinguish between the two,
 *  at which point type constructor classes would have to be generic in
 *  "actual type" vs "partial knowledge of type".
 * <li>Similarly, and EMPTY type (also known as VOID, ZERO or FALSE, in other contexts)
 *  is a type that has no instance, whereas the BOTTOM type is the type analysis that says
 *  that there is no possible runtime type for the given object, which may imply that
 *  the point in the program at which the object is evaluated cannot be reached, etc.
 * </ul>
 * So for now, we have puns between TOP and ANY, BOTTOM and EMPTY, between runtime (eval) and
 * validation-time (validate). Yet in the future, we may need to make a clear distinction,
 * especially if we are to have types such List(Any) vs List(Top), which contains the former,
 * but also plenty of other quite distinct types. And yet in a future future, the TOP type
 * would not be represented explicitly, instead a new type variable would be inserted everywhere
 * a type is unknown, to be unified with further type information as it becomes available.
 */
// TODO(bazel-team): move the FunctionType side-effect out of the type object
// and into the validation environment.
public abstract class SkylarkType implements Serializable {

  // The main primitives to override in subclasses

  /** Is the given value an element of this type? By default, no (empty type) */
  public boolean contains(Object value) {
    return false;
  }

  /**
   * intersectWith() is the internal method from which function intersection(t1, t2) is computed.
   * OVERRIDE this method in your classes, but DO NOT TO CALL it: only call intersection().
   * When computing intersection(t1, t2), whichever type defined before the other
   * knows nothing about the other and about their intersection, and returns BOTTOM;
   * the other knows about the former, and returns their intersection (which may be BOTTOM).
   * intersection() will call in one order then the other, and return whichever answer
   * isn't BOTTOM, if any. By default, types are disjoint and their intersection is BOTTOM.
   */
  // TODO(bazel-team): should we define and use an Exception instead?
  protected SkylarkType intersectWith(SkylarkType other) {
    return BOTTOM;
  }

  /** @return true if any object of this SkylarkType can be cast to that Java class */
  public boolean canBeCastTo(Class<?> type) {
    return SkylarkType.of(type).includes(this);
  }

  /** @return the smallest java Class known to contain all elements of this type */
  // Note: most user-code should be using a variant that throws an Exception
  // if the result is Object.class but the type isn't TOP.
  public Class<?> getType() {
    return Object.class;
  }

  // The actual intersection function for users to use

  public static SkylarkType intersection(SkylarkType t1, SkylarkType t2) {
    if (t1.equals(t2)) {
      return t1;
    }
    SkylarkType t = t1.intersectWith(t2);
    if (t == BOTTOM) {
      return t2.intersectWith(t1);
    } else {
      return t;
    }
  }

  public boolean includes(SkylarkType other) {
    return intersection(this, other).equals(other);
  }

  public SkylarkType getArgType() {
    return TOP;
  }

  private static final class Empty {} // Empty type, used as basis for Bottom

  // Notable types

  /** A singleton for the TOP type, that at analysis time means that any type is possible. */
  @AutoCodec public static final Simple TOP = new Top();

  /** A singleton for the BOTTOM type, that contains no element */
  @AutoCodec public static final Simple BOTTOM = new Bottom();

  /** NONE, the Unit type, isomorphic to Void, except its unique element prints as None */
  // Note that we currently consider at validation time that None is in every type,
  // by declaring its type as TOP instead of NONE, even though at runtime,
  // we reject None from all types but NONE, and in particular from e.g. lists of Files.
  // TODO(bazel-team): resolve this inconsistency, one way or the other.
  @AutoCodec public static final Simple NONE = Simple.forClass(Runtime.NoneType.class);

  /** The STRING type, for strings */
  @AutoCodec public static final Simple STRING = Simple.forClass(String.class);

  /** The INTEGER type, for 32-bit signed integers */
  @AutoCodec public static final Simple INT = Simple.forClass(Integer.class);

  /** The BOOLEAN type, that contains TRUE and FALSE */
  @AutoCodec public static final Simple BOOL = Simple.forClass(Boolean.class);

  /** The FUNCTION type, that contains all functions, otherwise dynamically typed at call-time */
  @AutoCodec
  public static final SkylarkFunctionType FUNCTION = new SkylarkFunctionType("unknown", TOP);

  /** The DICT type, that contains SkylarkDict */
  @AutoCodec public static final Simple DICT = Simple.forClass(SkylarkDict.class);

  /** The SEQUENCE type, that contains lists and tuples */
  // TODO(bazel-team): this was added for backward compatibility with the BUILD language,
  // that doesn't make a difference between list and tuple, so that functions can be declared
  // that keep not making the difference. Going forward, though, we should investigate whether
  // we ever want to use this type, and if not, make sure no existing client code uses it.
  @AutoCodec public static final Simple SEQUENCE = Simple.forClass(SkylarkList.class);

  /** The LIST type, that contains all MutableList-s */
  @AutoCodec public static final Simple LIST = Simple.forClass(MutableList.class);

  /** The TUPLE type, that contains all Tuple-s */
  @AutoCodec public static final Simple TUPLE = Simple.forClass(Tuple.class);

  /** The STRING_LIST type, a MutableList of strings */
  @AutoCodec public static final SkylarkType STRING_LIST = Combination.of(LIST, STRING);

  /** The INT_LIST type, a MutableList of integers */
  @AutoCodec public static final SkylarkType INT_LIST = Combination.of(LIST, INT);

  /** The SET type, that contains all SkylarkNestedSet-s, and the generic combinator for them */
  @AutoCodec public static final Simple SET = Simple.forClass(SkylarkNestedSet.class);

  // Common subclasses of SkylarkType

  /** the Top type contains all objects */
  private static class Top extends Simple {

    private Top() {
      super(Object.class);
    }

    @Override public boolean contains(Object value) {
      return true;
    }
    @Override public SkylarkType intersectWith(SkylarkType other) {
      return other;
    }
    @Override public String toString() {
      return "Object";
    }
  }

  /** the Bottom type contains no element */
  private static class Bottom extends Simple {

    private Bottom() {
      super(Empty.class);
    }

    @Override public SkylarkType intersectWith(SkylarkType other) {
      return this;
    }
    @Override public String toString() {
      return "EmptyType";
    }
  }

  /** a Simple type contains the instance of a Java class */
  @VisibleForSerialization
  static class Simple extends SkylarkType {
    private final Class<?> type;

    private Simple(Class<?> type) {
      this.type = type;
    }

    @Override public boolean contains(Object value) {
      return value != null && type.isInstance(value);
    }
    @Override public Class<?> getType() {
      return type;
    }
    @Override public boolean equals(Object other) {
      if (other == null) {
        return false;
      }
      return this == other
          || (this.getClass() == other.getClass() && this.type.equals(((Simple) other).getType()));
    }
    @Override public int hashCode() {
      return 0x513973 + type.hashCode() * 503; // equal underlying types yield the same hashCode
    }
    @Override public String toString() {
      return EvalUtils.getDataTypeNameFromClass(type);
    }
    @Override public boolean canBeCastTo(Class<?> type) {
      return this.type == type || super.canBeCastTo(type);
    }

    private static final LoadingCache<Class<?>, Simple> simpleCache =
        CacheBuilder.newBuilder()
            .build(
                new CacheLoader<Class<?>, Simple>() {
                  @Override
                  public Simple load(Class<?> type) {
                    return create(type);
                  }
                });

    private static Simple create(Class<?> type) {
      Simple simple;
      if (type == Object.class) {
        // Note that this is a bad encoding for "anything", not for "everything", i.e.
        // for skylark there isn't a type that contains everything, but there's a Top type
        // that corresponds to not knowing yet which more special type it will be.
        simple = TOP;
      } else if (type == Empty.class) {
        simple = BOTTOM;
      } else {
        // Consider all classes that have the same EvalUtils.getSkylarkType() as equivalent,
        // as a substitute to handling inheritance.
        Class<?> skylarkType = EvalUtils.getSkylarkType(type);
        if (skylarkType != type) {
          simple = Simple.forClass(skylarkType);
        } else {
          simple = new Simple(type);
        }
      }
      return simple;
    }

    /**
     * The way to create a Simple type.
     *
     * @param type a Class
     * @return the Simple type that contains exactly the instances of that Class
     */
    // Only call this method from SkylarkType. Calling it from outside SkylarkType leads to
    // circular dependencies in class initialization, showing up as an NPE while initializing NONE.
    // You actually want to call SkylarkType.of().
    private static Simple forClass(Class<?> type) {
      return simpleCache.getUnchecked(type);
    }
  }

  /** Combination of a generic type and an argument type */
  @AutoCodec
  public static class Combination extends SkylarkType {
    // For the moment, we can only combine a Simple type with a Simple type,
    // and the first one has to be a Java generic class,
    // and in practice actually one of SkylarkList or SkylarkNestedSet
    private final SkylarkType genericType; // actually always a Simple, for now.
    private final SkylarkType argType; // not always Simple

    @VisibleForSerialization
    Combination(SkylarkType genericType, SkylarkType argType) {
      this.genericType = genericType;
      this.argType = argType;
    }

    @Override
    public boolean contains(Object value) {
      // The empty collection is member of compatible types
      if (value == null || !genericType.contains(value)) {
        return false;
      } else {
        SkylarkType valueArgType = getGenericArgType(value);
        return valueArgType == TOP // empty objects are universal
            || argType.includes(valueArgType);
      }
    }
    @Override public SkylarkType intersectWith(SkylarkType other) {
      // For now, we only accept generics with a single covariant parameter
      if (genericType.equals(other)) {
        return this;
      }
      if (other instanceof Combination) {
        SkylarkType generic = genericType.intersectWith(((Combination) other).getGenericType());
        if (generic == BOTTOM) {
          return BOTTOM;
        }
        SkylarkType arg = intersection(argType, other.getArgType());
        if (arg == BOTTOM) {
          return BOTTOM;
        }
        return Combination.of(generic, arg);
      }
      if (other instanceof Simple) {
        SkylarkType generic = genericType.intersectWith(other);
        if (generic == BOTTOM) {
          return BOTTOM;
        }
        return SkylarkType.of(generic, getArgType());
      }
      return BOTTOM;
    }

    @Override public boolean equals(Object other) {
      if (other == null) {
        return false;
      }
      if (this == other) {
        return true;
      } else if (this.getClass() == other.getClass()) {
        Combination o = (Combination) other;
        return genericType.equals(o.getGenericType())
            && argType.equals(o.getArgType());
      } else {
        return false;
      }
    }
    @Override public int hashCode() {
      // equal underlying types yield the same hashCode
      return 0x20B14A71 + genericType.hashCode() * 1009 + argType.hashCode() * 1013;
    }
    @Override public Class<?> getType() {
      return genericType.getType();
    }
    SkylarkType getGenericType() {
      return genericType;
    }
    @Override
    public SkylarkType getArgType() {
      return argType;
    }
    @Override public String toString() {
      return genericType + " of " + argType + "s";
    }

    private static final Interner<Combination> combinationInterner =
        BlazeInterners.newWeakInterner();

    public static SkylarkType of(SkylarkType generic, SkylarkType argument) {
      // assume all combinations with TOP are the same as the simple type, and canonicalize.
      Preconditions.checkArgument(generic instanceof Simple);
      if (argument == TOP) {
        return generic;
      } else {
        return combinationInterner.intern(new Combination(generic, argument));
      }
    }
    public static SkylarkType of(Class<?> generic, Class<?> argument) {
      return of(Simple.forClass(generic), Simple.forClass(argument));
    }
  }

  /** Union types, used a lot in "dynamic" languages such as Python or Skylark */
  @AutoCodec
  public static class Union extends SkylarkType {
    private final ImmutableList<SkylarkType> types;

    @VisibleForSerialization
    Union(ImmutableList<SkylarkType> types) {
      this.types = types;
    }

    @Override
    public boolean contains(Object value) {
      for (SkylarkType type : types) {
        if (type.contains(value)) {
          return true;
        }
      }
      return false;
    }
    @Override public boolean equals(Object other) {
      if (other == null) {
        return false;
      }
      if (this.getClass() == other.getClass()) {
        Union o = (Union) other;
        if (types.containsAll(o.types) && o.types.containsAll(types)) {
          return true;
        }
      }
      return false;
    }
    @Override public int hashCode() {
      // equal underlying types yield the same hashCode
      int h = 0x4104;
      for (SkylarkType type : types) {
        // Important: addition is commutative, like Union
        h += type.hashCode();
      }
      return h;
    }
    @Override public String toString() {
      return Joiner.on(" or ").join(types);
    }
    public static List<SkylarkType> addElements(List<SkylarkType> list, SkylarkType type) {
      if (type instanceof Union) {
        list.addAll(((Union) type).types);
      } else if (type != BOTTOM) {
        list.add(type);
      }
      return list;
    }
    @Override public SkylarkType intersectWith(SkylarkType other) {
      List<SkylarkType> otherTypes = addElements(new ArrayList<>(), other);
      List<SkylarkType> results = new ArrayList<>();
      for (SkylarkType element : types) {
        for (SkylarkType otherElement : otherTypes) {
          addElements(results, intersection(element, otherElement));
        }
      }
      return Union.of(results);
    }
    public static SkylarkType of(List<SkylarkType> types) {
      // When making the union of many types,
      // canonicalize them into elementary (non-Union) types,
      // and then eliminate trivially redundant types from the list.

      // list of all types in the input
      ArrayList<SkylarkType> elements = new ArrayList<>();
      for (SkylarkType type : types) {
        addElements(elements, type);
      }

      // canonicalized list of types
      ArrayList<SkylarkType> canonical = new ArrayList<>();

      for (SkylarkType newType : elements) {
        boolean done = false; // done with this element?
        int i = 0;
        for (SkylarkType existingType : canonical) {
          SkylarkType both = intersection(newType, existingType);
          if (newType.equals(both)) { // newType already included
            done = true;
            break;
          } else if (existingType.equals(both)) { // newType supertype of existingType
            canonical.set(i, newType);
            done = true;
            break;
          }
        }
        if (!done) {
          canonical.add(newType);
        }
      }
      if (canonical.isEmpty()) {
        return BOTTOM;
      } else if (canonical.size() == 1) {
        return canonical.get(0);
      } else {
        return new Union(ImmutableList.copyOf(canonical));
      }
    }
    public static SkylarkType of(SkylarkType... types) {
      return of(Arrays.asList(types));
    }
    public static SkylarkType of(SkylarkType t1, SkylarkType t2) {
      return of(ImmutableList.of(t1, t2));
    }
    public static SkylarkType of(Class<?> t1, Class<?> t2) {
      return of(Simple.forClass(t1), Simple.forClass(t2));
    }
  }

  public static SkylarkType of(Class<?> type) {
    if (SkylarkNestedSet.class.isAssignableFrom(type)) {
      return SET;
    } else if (BaseFunction.class.isAssignableFrom(type)) {
      return new SkylarkFunctionType("unknown", TOP);
    } else {
      return Simple.forClass(type);
    }
  }

  public static SkylarkType of(SkylarkType t1, SkylarkType t2) {
    return Combination.of(t1, t2);
  }
  public static SkylarkType of(Class<?> t1, Class<?> t2) {
    return Combination.of(t1, t2);
  }

  /** A class representing the type of a Skylark function. */
  @AutoCodec
  public static final class SkylarkFunctionType extends SkylarkType {
    private final String name;
    @Nullable private final SkylarkType returnType;

    @Override public SkylarkType intersectWith(SkylarkType other) {
      // This gives the wrong result if both return types are incompatibly updated later!
      if (other instanceof SkylarkFunctionType) {
        SkylarkFunctionType fun = (SkylarkFunctionType) other;
        SkylarkType type1 = returnType == null ? TOP : returnType;
        SkylarkType type2 = fun.returnType == null ? TOP : fun.returnType;
        SkylarkType bothReturnType = intersection(returnType, fun.returnType);
        if (type1.equals(bothReturnType)) {
          return this;
        } else if (type2.equals(bothReturnType)) {
          return fun;
        } else {
          return new SkylarkFunctionType(name, bothReturnType);
        }
      } else {
        return BOTTOM;
      }
    }
    @Override public Class<?> getType() {
      return BaseFunction.class;
    }
    @Override public String toString() {
      return (returnType == TOP || returnType == null ? "" : returnType + "-returning ")
          + "function";
    }

    @Override
    public boolean contains(Object value) {
      // This returns true a bit too much, not looking at the result type.
      return value instanceof BaseFunction;
    }

    public static SkylarkFunctionType of(String name, SkylarkType returnType) {
      return new SkylarkFunctionType(name, returnType);
    }

    @VisibleForSerialization
    SkylarkFunctionType(String name, SkylarkType returnType) {
      this.name = name;
      this.returnType = returnType;
    }
  }

  // Utility functions regarding types

  public static SkylarkType typeOf(Object value) {
    if (value == null) {
      return BOTTOM;
    } else if (value instanceof SkylarkNestedSet) {
      return of(SET, ((SkylarkNestedSet) value).getContentType());
    } else {
      return Simple.forClass(value.getClass());
    }
  }

  public static SkylarkType getGenericArgType(Object value) {
    if (value instanceof SkylarkNestedSet) {
      return ((SkylarkNestedSet) value).getContentType();
    } else {
      return TOP;
    }
  }

  private static boolean isTypeAllowedInSkylark(Object object) {
    if (object instanceof NestedSet<?>) {
      return false;
    } else if (object instanceof List<?> && !(object instanceof SkylarkList)) {
      return false;
    }
    return true;
  }

  /**
   * Throws EvalException if the type of the object is not allowed to be present in Skylark.
   */
  static void checkTypeAllowedInSkylark(Object object, Location loc) throws EvalException {
    // TODO(bazel-team): Unify this check with the logic in EvalUtils.getSkylarkType(). Might
    // break some providers whose contents don't implement SkylarkValue, aren't wrapped in
    // SkylarkList, etc.
    if (!isTypeAllowedInSkylark(object)) {
      throw new EvalException(
          loc,
          "internal error: type '" + object.getClass().getSimpleName() + "' is not allowed as a "
              + "Skylark value (checkTypeAllowedInSkylark() failed)");
    }
  }

  /**
   * General purpose type-casting facility.
   *
   * @param value - the actual value of the parameter
   * @param type - the expected Class for the value
   * @param loc - the location info used in the EvalException
   * @param format - a format String
   * @param args - arguments to format, in case there's an exception
   */
  public static <T> T cast(Object value, Class<T> type,
      Location loc, String format, Object... args) throws EvalException {
    try {
      return type.cast(value);
    } catch (ClassCastException e) {
      throw new EvalException(loc, String.format(format, args));
    }
  }

  /**
   * General purpose type-casting facility.
   *
   * @param value - the actual value of the parameter
   * @param genericType - a generic class of one argument for the value
   * @param argType - a covariant argument for the generic class
   * @param loc - the location info used in the EvalException
   * @param format - a format String
   * @param args - arguments to format, in case there's an exception
   */
  @SuppressWarnings("unchecked")
  public static <T> T cast(Object value, Class<T> genericType, Class<?> argType,
      Location loc, String format, Object... args) throws EvalException {
    if (of(genericType, argType).contains(value)) {
      return (T) value;
    } else {
      throw new EvalException(loc, String.format(format, args));
    }
  }

  /**
   * Cast a Map object into an Iterable of Map entries of the given key, value types.
   * @param obj the Map object, where null designates an empty map
   * @param keyType the class of map keys
   * @param valueType the class of map values
   * @param what a string indicating what this is about, to include in case of error
   */
  @SuppressWarnings("unchecked")
  public static <KEY_TYPE, VALUE_TYPE> Map<KEY_TYPE, VALUE_TYPE> castMap(Object obj,
      Class<KEY_TYPE> keyType, Class<VALUE_TYPE> valueType, String what)
      throws EvalException {
    if (obj == null) {
      return ImmutableMap.of();
    }
    if (!(obj instanceof Map<?, ?>)) {
      throw new EvalException(
          null,
          String.format(
              "expected a dictionary for '%s' but got '%s' instead",
              what, EvalUtils.getDataTypeName(obj)));
    }

    for (Map.Entry<?, ?> input : ((Map<?, ?>) obj).entrySet()) {
      if (!keyType.isAssignableFrom(input.getKey().getClass())
          || !valueType.isAssignableFrom(input.getValue().getClass())) {
        throw new EvalException(
            null,
            String.format(
                "expected <%s, %s> type for '%s' but got <%s, %s> instead",
                keyType.getSimpleName(),
                valueType.getSimpleName(),
                what,
                EvalUtils.getDataTypeName(input.getKey()),
                EvalUtils.getDataTypeName(input.getValue())));
      }
    }

    return (Map<KEY_TYPE, VALUE_TYPE>) obj;
  }

  private static Class<?> getGenericTypeFromMethod(Method method) {
    // This is where we can infer generic type information, so SkylarkNestedSets can be
    // created in a safe way. Eventually we should probably do something with Lists and Maps too.
    ParameterizedType t = (ParameterizedType) method.getGenericReturnType();
    Type type = t.getActualTypeArguments()[0];
    if (type instanceof Class) {
      return (Class<?>) type;
    }
    if (type instanceof WildcardType) {
      WildcardType wildcard = (WildcardType) type;
      Type upperBound = wildcard.getUpperBounds()[0];
      if (upperBound instanceof Class) {
        // i.e. List<? extends SuperClass>
        return (Class<?>) upperBound;
      }
    }
    // It means someone annotated a method with @SkylarkCallable with no specific generic type info.
    // We shouldn't annotate methods which return List<?> or List<T>.
    throw new IllegalStateException("Cannot infer type from method signature " + method);
  }

  /**
   * Converts an object retrieved from a Java method to a Skylark-compatible type.
   */
  static Object convertToSkylark(Object object, Method method, @Nullable Environment env) {
    if (object instanceof NestedSet<?>) {
      return SkylarkNestedSet.of(
          SkylarkType.of(getGenericTypeFromMethod(method)), (NestedSet<?>) object);
    }
    return convertToSkylark(object, env);
  }

  /**
   * Converts an object to a Skylark-compatible type if possible.
   */
  public static Object convertToSkylark(Object object, @Nullable Environment env) {
    return convertToSkylark(object, env == null ? null : env.mutability());
  }

  /**
   * Converts an object to a Skylark-compatible type if possible.
   */
  public static Object convertToSkylark(Object object, @Nullable Mutability mutability) {
    if (object instanceof List && !(object instanceof SkylarkList)) {
      return MutableList.copyOf(mutability, (List<?>) object);
    }
    if (object instanceof SkylarkValue) {
      return object;
    }
    if (object instanceof Map) {
      return SkylarkDict.<Object, Object>copyOf(mutability, (Map<?, ?>) object);
    }
    // TODO(bazel-team): ensure everything is a SkylarkValue at all times.
    // Preconditions.checkArgument(EvalUtils.isSkylarkAcceptable(
    //    object.getClass()),
    //    "invalid object %s of class %s not convertible to a Skylark value",
    //    object,
    //    object.getClass());
    return object;
  }

  public static void checkType(Object object, Class<?> type, @Nullable Object description)
      throws EvalException {
    if (!type.isInstance(object)) {
      throw new EvalException(
          null,
          Printer.format(
              "expected type '%r' %sbut got type '%s' instead",
              type,
              description == null ? "" : String.format("for %s ", description),
              EvalUtils.getDataTypeName(object)));
      }
  }
}