bazel syntax: rationalize the type check operators

This change introduces {Dict,Sequence}.{,noneable}Cast.
These four conversion operators cast an arbitrary value
to a Sequence<T> or Dict<K,V>, replacing these 9 previous functions:
- Dict.{castSkylarkDictOrNoneToDict,getContents}
- Sequence.{castList,castSkylarkListOrNoneToList,getContents}
- SkylarkType.{cast,cast',checkType,castMap}

The functions don't allocate an unmodifiable wrapper,
as Dict and List are already unmodifiable through the java.util
interfaces, so this is just wasteful allocation and indirection.
Also, there is no a priori allocation of Formattable error messages.

A number of messes (e.g. unsound casts) were cleaned up throughout.

The new operators do not accept a Location, and report error
using Starlark.errorf. (This whole CL started as a subtask of a
subtask to eliminate the Location parameter of EvalException,
and this sub-sub-task is an experiment to see whether removing
Location parameters that don't correspond to program counter
locations is a UI regression. SRCTU.createTarget is the only
place where this appears to be a problem. For now I've added a
small kludge, but in a follow-up I will change it to report
events, not throw exceptions, so that it can report multiple
errors at arbitrary locations.

Depset might benefit from a similar {noneable,}Cast treatment,
but this too is left for a follow-up.

This is a breaking API change for copybara.

RELNOTES: N/A
PiperOrigin-RevId: 306925434
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/OutputGroupInfo.java b/src/main/java/com/google/devtools/build/lib/analysis/OutputGroupInfo.java
index 3b6c73b..d61e56f 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/OutputGroupInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/OutputGroupInfo.java
@@ -283,10 +283,9 @@
 
     @Override
     public OutputGroupInfoApi constructor(Dict<?, ?> kwargs) throws EvalException {
-      Map<String, Object> kwargsMap = kwargs.getContents(String.class, Object.class, "kwargs");
-
       ImmutableMap.Builder<String, NestedSet<Artifact>> builder = ImmutableMap.builder();
-      for (Map.Entry<String, Object> entry : kwargsMap.entrySet()) {
+      for (Map.Entry<String, Object> entry :
+          Dict.cast(kwargs, String.class, Object.class, "kwargs").entrySet()) {
         builder.put(
             entry.getKey(),
             SkylarkRuleConfiguredTargetUtil.convertToOutputGroupValue(
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TemplateVariableInfo.java b/src/main/java/com/google/devtools/build/lib/analysis/TemplateVariableInfo.java
index 647d910..f93c1ab 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/TemplateVariableInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TemplateVariableInfo.java
@@ -43,8 +43,7 @@
     @Override
     public TemplateVariableInfo templateVariableInfo(Dict<?, ?> vars, StarlarkThread thread)
         throws EvalException {
-      Map<String, String> varsMap =
-          Dict.castSkylarkDictOrNoneToDict(vars, String.class, String.class, "vars");
+      Map<String, String> varsMap = Dict.noneableCast(vars, String.class, String.class, "vars");
       return new TemplateVariableInfo(ImmutableMap.copyOf(varsMap), thread.getCallerLocation());
     }
   }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/StarlarkDefinedConfigTransition.java b/src/main/java/com/google/devtools/build/lib/analysis/config/StarlarkDefinedConfigTransition.java
index c7e0fec..478520e 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/StarlarkDefinedConfigTransition.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/StarlarkDefinedConfigTransition.java
@@ -204,7 +204,6 @@
      */
     // TODO(bazel-team): integrate dict-of-dicts return type with ctx.split_attr
     @Override
-    @SuppressWarnings("rawtypes")
     public ImmutableMap<String, Map<String, Object>> evaluate(
         Map<String, Object> previousSettings, StructImpl attributeMapper, EventHandler eventHandler)
         throws EvalException, InterruptedException {
@@ -227,16 +226,13 @@
         // TODO(bazel-team): integrate keys with ctx.split_attr. Currently ctx.split_attr always
         // keys on cpu value - we should be able to key on the keys returned here.
         try {
-          @SuppressWarnings("rawtypes")
-          Map<String, Dict> dictOfDict =
-              ((Dict<?, ?>) result)
-                  .getContents(String.class, Dict.class, "dictionary of options dictionaries");
+          Map<String, ?> dictOfDict =
+              Dict.cast(result, String.class, Dict.class, "dictionary of options dictionaries");
           ImmutableMap.Builder<String, Map<String, Object>> builder = ImmutableMap.builder();
-          for (Map.Entry<String, Dict> entry : dictOfDict.entrySet()) { // rawtypes error
-            Map<String, Object> dict =
-                ((Dict<?, ?>) entry.getValue())
-                    .getContents(String.class, Object.class, "an option dictionary");
-            builder.put(entry.getKey(), dict);
+          for (Map.Entry<String, ?> entry : dictOfDict.entrySet()) {
+            builder.put(
+                entry.getKey(),
+                Dict.cast(entry.getValue(), String.class, Object.class, "an option dictionary"));
           }
           return builder.build();
         } catch (EvalException e) {
@@ -246,8 +242,7 @@
           // Try if this is a patch transition.
           return ImmutableMap.of(
               PATCH_TRANSITION_KEY,
-              ((Dict<?, ?>) result)
-                  .getContents(String.class, Object.class, "dictionary of options"));
+              Dict.cast(result, String.class, Object.class, "dictionary of options"));
         } catch (EvalException e) {
           throw new EvalException(impl.getLocation(), e.getMessage());
         }
@@ -256,12 +251,11 @@
         try {
           int i = 0;
           for (Dict<?, ?> toOptions :
-              ((Sequence<?>) result)
-                  .getContents(Dict.class, "dictionary of options dictionaries")) {
+              Sequence.cast(result, Dict.class, "dictionary of options dictionaries")) {
             // TODO(b/146347033): Document this behavior.
             builder.put(
                 Integer.toString(i++),
-                toOptions.getContents(String.class, Object.class, "dictionary of options"));
+                Dict.cast(toOptions, String.class, Object.class, "dictionary of options"));
           }
         } catch (EvalException e) {
           throw new EvalException(impl.getLocation(), e.getMessage());
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/platform/PlatformInfo.java b/src/main/java/com/google/devtools/build/lib/analysis/platform/PlatformInfo.java
index 034d41b..c084542 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/platform/PlatformInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/platform/PlatformInfo.java
@@ -80,11 +80,12 @@
       }
       if (!constraintValuesUnchecked.isEmpty()) {
         builder.addConstraints(
-            constraintValuesUnchecked.getContents(ConstraintValueInfo.class, "constraint_values"));
+            Sequence.cast(
+                constraintValuesUnchecked, ConstraintValueInfo.class, "constraint_values"));
       }
       if (execPropertiesUnchecked != null) {
-        Map<String, String> execProperties =
-            Dict.castSkylarkDictOrNoneToDict(
+        Dict<String, String> execProperties =
+            Dict.noneableCast(
                 execPropertiesUnchecked, String.class, String.class, "exec_properties");
         builder.setExecProperties(ImmutableMap.copyOf(execProperties));
       }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
index 3b1d5db..cd1d711 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
@@ -200,7 +200,7 @@
         inputs instanceof Depset
             ? ((Depset) inputs).getSetFromParam(Artifact.class, "inputs")
             : NestedSetBuilder.<Artifact>compileOrder()
-                .addAll(((Sequence<?>) inputs).getContents(Artifact.class, "inputs"))
+                .addAll(Sequence.cast(inputs, Artifact.class, "inputs"))
                 .build();
     Action action =
         new PseudoAction<>(
@@ -404,7 +404,7 @@
       if (argumentList.size() > 0) {
         throw Starlark.errorf("'arguments' must be empty if 'command' is a sequence of strings");
       }
-      List<String> command = commandList.getContents(String.class, "command");
+      List<String> command = Sequence.cast(commandList, String.class, "command");
       builder.setShellCommand(command);
     } else {
       throw new EvalException(
@@ -479,7 +479,7 @@
       throws EvalException {
     Iterable<Artifact> inputArtifacts;
     if (inputs instanceof Sequence) {
-      inputArtifacts = ((Sequence<?>) inputs).getContents(Artifact.class, "inputs");
+      inputArtifacts = Sequence.cast(inputs, Artifact.class, "inputs");
       builder.addInputs(inputArtifacts);
     } else {
       NestedSet<Artifact> inputSet = ((Depset) inputs).getSetFromParam(Artifact.class, "inputs");
@@ -487,7 +487,7 @@
       inputArtifacts = inputSet.toList();
     }
 
-    List<Artifact> outputArtifacts = outputs.getContents(Artifact.class, "outputs");
+    List<Artifact> outputArtifacts = Sequence.cast(outputs, Artifact.class, "outputs");
     if (outputArtifacts.isEmpty()) {
       throw Starlark.errorf("param 'outputs' may not be empty");
     }
@@ -513,7 +513,7 @@
     if (toolsUnchecked != Starlark.UNBOUND) {
       Iterable<?> toolsIterable;
       if (toolsUnchecked instanceof Sequence) {
-        toolsIterable = ((Sequence<?>) toolsUnchecked).getContents(Object.class, "tools");
+        toolsIterable = Sequence.cast(toolsUnchecked, Object.class, "tools");
       } else {
         toolsIterable = ((Depset) toolsUnchecked).getSet().toList();
       }
@@ -583,8 +583,7 @@
     builder.setMnemonic(mnemonic);
     if (envUnchecked != Starlark.NONE) {
       builder.setEnvironment(
-          ImmutableMap.copyOf(
-              Dict.castSkylarkDictOrNoneToDict(envUnchecked, String.class, String.class, "env")));
+          ImmutableMap.copyOf(Dict.cast(envUnchecked, String.class, String.class, "env")));
     }
     if (progressMessage != Starlark.NONE) {
       builder.setProgressMessageNonLazy((String) progressMessage);
@@ -602,8 +601,7 @@
 
     if (inputManifestsUnchecked != Starlark.NONE) {
       for (RunfilesSupplier supplier :
-          Sequence.castSkylarkListOrNoneToList(
-              inputManifestsUnchecked, RunfilesSupplier.class, "runfiles suppliers")) {
+          Sequence.cast(inputManifestsUnchecked, RunfilesSupplier.class, "runfiles suppliers")) {
         builder.addRunfilesSupplier(supplier);
       }
     }
@@ -630,9 +628,7 @@
     context.checkMutable("actions.expand_template");
     ImmutableList.Builder<Substitution> substitutionsBuilder = ImmutableList.builder();
     for (Map.Entry<String, String> substitution :
-        substitutionsUnchecked
-            .getContents(String.class, String.class, "substitutions")
-            .entrySet()) {
+        Dict.cast(substitutionsUnchecked, String.class, String.class, "substitutions").entrySet()) {
       // ParserInput.create(Path) uses Latin1 when reading BUILD files, which might
       // contain UTF-8 encoded symbols as part of template substitution.
       // As a quick fix, the substitution values are corrected before being passed on.
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkAttr.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkAttr.java
index 38609a3..bae0993 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkAttr.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkAttr.java
@@ -18,7 +18,6 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.analysis.config.ExecutionTransitionFactory;
 import com.google.devtools.build.lib.analysis.config.HostTransition;
 import com.google.devtools.build.lib.analysis.config.StarlarkDefinedConfigTransition;
@@ -49,7 +48,6 @@
 import com.google.devtools.build.lib.syntax.Module;
 import com.google.devtools.build.lib.syntax.Printer;
 import com.google.devtools.build.lib.syntax.Sequence;
-import com.google.devtools.build.lib.syntax.SkylarkType;
 import com.google.devtools.build.lib.syntax.Starlark;
 import com.google.devtools.build.lib.syntax.StarlarkFunction;
 import com.google.devtools.build.lib.syntax.StarlarkThread;
@@ -70,6 +68,8 @@
 
   // Arguments
 
+  // TODO(adonovan): opt: this class does a lot of redundant hashtable lookups.
+
   private static boolean containsNonNoneKey(Map<String, Object> arguments, String key) {
     return arguments.containsKey(key) && arguments.get(key) != Starlark.NONE;
   }
@@ -82,9 +82,7 @@
       builder.allowedFileTypes(FileTypeSet.NO_FILE);
     } else if (fileTypesObj instanceof Sequence) {
       ImmutableList<String> arg =
-          ImmutableList.copyOf(
-              Sequence.castSkylarkListOrNoneToList(
-                  fileTypesObj, String.class, "allow_files argument"));
+          ImmutableList.copyOf(Sequence.cast(fileTypesObj, String.class, "allow_files argument"));
       builder.allowedFileTypes(FileType.of(arg));
     } else {
       throw new EvalException(null, attr + " should be a boolean or a string list");
@@ -146,9 +144,11 @@
       }
     }
 
-    for (String flag :
-        Sequence.castSkylarkListOrNoneToList(arguments.get(FLAGS_ARG), String.class, FLAGS_ARG)) {
-      builder.setPropertyFlag(flag);
+    Object flagsArg = arguments.get(FLAGS_ARG);
+    if (flagsArg != null) {
+      for (String flag : Sequence.noneableCast(flagsArg, String.class, FLAGS_ARG)) {
+        builder.setPropertyFlag(flag);
+      }
     }
 
     if (containsNonNoneKey(arguments, MANDATORY_ARG) && (Boolean) arguments.get(MANDATORY_ARG)) {
@@ -220,21 +220,22 @@
     Object ruleClassesObj = arguments.get(ALLOW_RULES_ARG);
     if (ruleClassesObj != null && ruleClassesObj != Starlark.NONE) {
       builder.allowedRuleClasses(
-          Sequence.castSkylarkListOrNoneToList(
+          Sequence.cast(
               ruleClassesObj, String.class, "allowed rule classes for attribute definition"));
     }
 
-    List<Object> values =
-        Sequence.castSkylarkListOrNoneToList(arguments.get(VALUES_ARG), Object.class, VALUES_ARG);
-    if (!Iterables.isEmpty(values)) {
-      builder.allowedValues(new AllowedValueSet(values));
+    Object valuesArg = arguments.get(VALUES_ARG);
+    if (valuesArg != null) {
+      List<Object> values = Sequence.noneableCast(valuesArg, Object.class, VALUES_ARG);
+      if (!values.isEmpty()) {
+        builder.allowedValues(new AllowedValueSet(values));
+      }
     }
 
     if (containsNonNoneKey(arguments, PROVIDERS_ARG)) {
       Object obj = arguments.get(PROVIDERS_ARG);
-      SkylarkType.checkType(obj, Sequence.class, PROVIDERS_ARG);
       ImmutableList<ImmutableSet<SkylarkProviderIdentifier>> providersList =
-          buildProviderPredicate((Sequence<?>) obj, PROVIDERS_ARG);
+          buildProviderPredicate(Sequence.cast(obj, Object.class, PROVIDERS_ARG), PROVIDERS_ARG);
 
       // If there is at least one empty set, there is no restriction.
       if (providersList.stream().noneMatch(ImmutableSet::isEmpty)) {
@@ -284,10 +285,7 @@
 
     if (containsNonNoneKey(arguments, ASPECTS_ARG)) {
       Object obj = arguments.get(ASPECTS_ARG);
-      SkylarkType.checkType(obj, Sequence.class, ASPECTS_ARG);
-
-      List<SkylarkAspect> aspects = ((Sequence<?>) obj).getContents(SkylarkAspect.class, "aspects");
-      for (SkylarkAspect aspect : aspects) {
+      for (SkylarkAspect aspect : Sequence.cast(obj, SkylarkAspect.class, "aspects")) {
         aspect.attachToAttribute(builder);
       }
     }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
index bde937a..d42fce7 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleClassFunctions.java
@@ -24,7 +24,6 @@
 import static com.google.devtools.build.lib.packages.Type.INTEGER;
 import static com.google.devtools.build.lib.packages.Type.STRING;
 import static com.google.devtools.build.lib.packages.Type.STRING_LIST;
-import static com.google.devtools.build.lib.syntax.SkylarkType.castMap;
 
 import com.google.common.base.Preconditions;
 import com.google.common.cache.CacheBuilder;
@@ -89,7 +88,6 @@
 import com.google.devtools.build.lib.syntax.Module;
 import com.google.devtools.build.lib.syntax.Printer;
 import com.google.devtools.build.lib.syntax.Sequence;
-import com.google.devtools.build.lib.syntax.SkylarkType;
 import com.google.devtools.build.lib.syntax.Starlark;
 import com.google.devtools.build.lib.syntax.StarlarkCallable;
 import com.google.devtools.build.lib.syntax.StarlarkFunction;
@@ -253,27 +251,12 @@
 
   @Override
   public Provider provider(String doc, Object fields, StarlarkThread thread) throws EvalException {
-    Collection<String> fieldNames = null;
-    if (fields instanceof Sequence) {
-      @SuppressWarnings("unchecked")
-      Sequence<String> list =
-          (Sequence<String>)
-              SkylarkType.cast(
-                  fields,
-                  Sequence.class,
-                  String.class,
-                  null,
-                  "Expected list of strings or dictionary of string -> string for 'fields'");
-      fieldNames = list;
-    } else if (fields instanceof Dict) {
-      Map<String, String> dict =
-          SkylarkType.castMap(
-              fields,
-              String.class,
-              String.class,
-              "Expected list of strings or dictionary of string -> string for 'fields'");
-      fieldNames = dict.keySet();
-    }
+    Collection<String> fieldNames =
+        fields instanceof Sequence
+            ? Sequence.cast(fields, String.class, "fields")
+            : fields instanceof Dict
+                ? Dict.cast(fields, String.class, String.class, "fields").keySet()
+                : null;
     return SkylarkProvider.createUnexportedSchemaful(fieldNames, thread.getCallerLocation());
   }
 
@@ -345,7 +328,7 @@
         builder.setImplicitOutputsFunction(
             new SkylarkImplicitOutputsFunctionWithMap(
                 ImmutableMap.copyOf(
-                    castMap(
+                    Dict.cast(
                         implicitOutputs,
                         String.class,
                         String.class,
@@ -358,10 +341,10 @@
     }
 
     builder.requiresConfigurationFragmentsBySkylarkModuleName(
-        fragments.getContents(String.class, "fragments"));
+        Sequence.cast(fragments, String.class, "fragments"));
     ConfigAwareRuleClassBuilder.of(builder)
         .requiresHostConfigurationFragmentsBySkylarkModuleName(
-            hostFragments.getContents(String.class, "host_fragments"));
+            Sequence.cast(hostFragments, String.class, "host_fragments"));
     builder.setConfiguredTargetFunction(implementation);
     builder.setRuleDefinitionEnvironmentLabelAndHashCode(
         (Label) Module.ofInnermostEnclosingStarlarkFunction(thread).getLabel(),
@@ -370,7 +353,7 @@
     builder.addRequiredToolchains(parseToolchains(toolchains, thread));
 
     if (execGroups != Starlark.NONE) {
-      builder.addExecGroups(castMap(execGroups, String.class, ExecGroup.class, "exec_group"));
+      builder.addExecGroups(Dict.cast(execGroups, String.class, ExecGroup.class, "exec_group"));
     }
 
     if (!buildSetting.equals(Starlark.NONE) && !cfg.equals(Starlark.NONE)) {
@@ -423,7 +406,7 @@
 
     if (attrs != Starlark.NONE) {
       for (Map.Entry<String, Descriptor> attr :
-          castMap(attrs, String.class, Descriptor.class, "attrs").entrySet()) {
+          Dict.cast(attrs, String.class, Descriptor.class, "attrs").entrySet()) {
         Descriptor attrDescriptor = attr.getValue();
         AttributeValueSource source = attrDescriptor.getValueSource();
         checkAttributeName(attr.getKey());
@@ -472,7 +455,7 @@
   private static ImmutableList<Label> parseToolchains(Sequence<?> inputs, StarlarkThread thread)
       throws EvalException {
     return parseLabels(
-        inputs.getContents(String.class, "toolchains"),
+        Sequence.cast(inputs, String.class, "toolchains"),
         BazelStarlarkContext.from(thread).getRepoMapping(),
         "toolchain");
   }
@@ -480,7 +463,7 @@
   private static ImmutableList<Label> parseExecCompatibleWith(
       Sequence<?> inputs, StarlarkThread thread) throws EvalException {
     return parseLabels(
-        inputs.getContents(String.class, "exec_compatible_with"),
+        Sequence.cast(inputs, String.class, "exec_compatible_with"),
         BazelStarlarkContext.from(thread).getRepoMapping(),
         "constraint");
   }
@@ -576,9 +559,9 @@
         SkylarkAttr.buildProviderPredicate(requiredAspectProvidersArg, "required_aspect_providers"),
         SkylarkAttr.getSkylarkProviderIdentifiers(providesArg),
         requiredParams.build(),
-        ImmutableSet.copyOf(fragments.getContents(String.class, "fragments")),
+        ImmutableSet.copyOf(Sequence.cast(fragments, String.class, "fragments")),
         HostTransition.INSTANCE,
-        ImmutableSet.copyOf(hostFragments.getContents(String.class, "host_fragments")),
+        ImmutableSet.copyOf(Sequence.cast(hostFragments, String.class, "host_fragments")),
         parseToolchains(toolchains, thread),
         applyToGeneratingRules);
   }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
index eba1161..0462f78 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleConfiguredTargetUtil.java
@@ -50,15 +50,14 @@
 import com.google.devtools.build.lib.packages.StructProvider;
 import com.google.devtools.build.lib.packages.TargetUtils;
 import com.google.devtools.build.lib.packages.Type;
-import com.google.devtools.build.lib.syntax.ClassObject;
 import com.google.devtools.build.lib.syntax.Depset;
+import com.google.devtools.build.lib.syntax.Dict;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.EvalExceptionWithStackTrace;
 import com.google.devtools.build.lib.syntax.EvalUtils;
 import com.google.devtools.build.lib.syntax.Location;
 import com.google.devtools.build.lib.syntax.Mutability;
 import com.google.devtools.build.lib.syntax.Sequence;
-import com.google.devtools.build.lib.syntax.SkylarkType;
 import com.google.devtools.build.lib.syntax.Starlark;
 import com.google.devtools.build.lib.syntax.StarlarkCallable;
 import com.google.devtools.build.lib.syntax.StarlarkSemantics;
@@ -226,7 +225,27 @@
             .getRuleClassObject()
             .getConfiguredTargetFunction()
             .getLocation();
-    addProviders(context, builder, target, loc);
+
+    // TODO(adonovan): clean up addProviders' error handling,
+    // reporting provider validity errors through ruleError
+    // where possible. This allows for multiple events, with independent
+    // locations, even for the same root cause.
+    // EvalException is the wrong exception for createTarget to throw
+    // since it is neither called by Starlark nor does it call Starlak.
+    //
+    // In the meantime, ensure that any EvalException has a location.
+    try {
+      addProviders(context, builder, target, loc);
+    } catch (EvalException ex) {
+      if (ex.getLocation() == null) {
+        // Prefer target struct's creation location in error messages.
+        if (target instanceof Info) {
+          loc = ((Info) target).getCreationLoc();
+        }
+        ex = new EvalException(loc, ex.getMessage());
+      }
+      throw ex;
+    }
 
     try {
       return builder.build();
@@ -235,36 +254,37 @@
     }
   }
 
-  private static void addOutputGroups(Object value, RuleConfiguredTargetBuilder builder)
+  private static void addOutputGroups(Object outputGroups, RuleConfiguredTargetBuilder builder)
       throws EvalException {
-    Map<String, StarlarkValue> outputGroups =
-        SkylarkType.castMap(value, String.class, StarlarkValue.class, "output_groups");
-
-    for (String outputGroup : outputGroups.keySet()) {
-      StarlarkValue objects = outputGroups.get(outputGroup);
-      NestedSet<Artifact> artifacts = convertToOutputGroupValue(outputGroup, objects);
+    for (Map.Entry<String, StarlarkValue> entry :
+        Dict.cast(outputGroups, String.class, StarlarkValue.class, "output_groups").entrySet()) {
+      String outputGroup = entry.getKey();
+      NestedSet<Artifact> artifacts = convertToOutputGroupValue(outputGroup, entry.getValue());
       builder.addOutputGroup(outputGroup, artifacts);
     }
   }
 
-  @SuppressWarnings("unchecked") // Casting Sequence to List<String> is checked by cast().
   private static void addInstrumentedFiles(
       StructImpl insStruct, RuleContext ruleContext, RuleConfiguredTargetBuilder builder)
       throws EvalException {
-    Location insLoc = insStruct.getCreationLoc();
     List<String> extensions = null;
     if (insStruct.getFieldNames().contains("extensions")) {
-      extensions = cast("extensions", insStruct, Sequence.class, String.class, insLoc);
+      extensions = Sequence.cast(insStruct.getValue("extensions"), String.class, "extensions");
     }
+
     List<String> dependencyAttributes = Collections.emptyList();
     if (insStruct.getFieldNames().contains("dependency_attributes")) {
       dependencyAttributes =
-          cast("dependency_attributes", insStruct, Sequence.class, String.class, insLoc);
+          Sequence.cast(
+              insStruct.getValue("dependency_attributes"), String.class, "dependency_attributes");
     }
+
     List<String> sourceAttributes = Collections.emptyList();
     if (insStruct.getFieldNames().contains("source_attributes")) {
-      sourceAttributes = cast("source_attributes", insStruct, Sequence.class, String.class, insLoc);
+      sourceAttributes =
+          Sequence.cast(insStruct.getValue("source_attributes"), String.class, "source_attributes");
     }
+
     InstrumentedFilesInfo instrumentedFilesProvider =
         CoverageCommon.createInstrumentedFilesInfo(
             ruleContext,
@@ -293,28 +313,23 @@
         }
       }
       return nestedSetBuilder.build();
-    } else {
-      Depset artifactsSet =
-          SkylarkType.cast(
-              objects,
-              Depset.class,
-              Artifact.class,
-              null,
-              typeErrorMessage,
-              outputGroup,
-              EvalUtils.getDataTypeName(objects, true));
+    }
+
+    if (objects instanceof Depset) {
       try {
-        return artifactsSet.getSet(Artifact.class);
+        return ((Depset) objects).getSet(Artifact.class);
       } catch (Depset.TypeException exception) {
         throw new EvalException(
             null,
             String.format(
                 typeErrorMessage,
                 outputGroup,
-                "depset of type '" + artifactsSet.getContentType() + "'"),
+                "depset of type '" + ((Depset) objects).getContentType() + "'"),
             exception);
       }
     }
+
+    throw Starlark.errorf(typeErrorMessage, outputGroup, Starlark.type(objects));
   }
 
   private static void addProviders(
@@ -333,8 +348,7 @@
       if (getProviderKey(loc, info).equals(StructProvider.STRUCT.getKey())) {
 
         if (context.getSkylarkSemantics().incompatibleDisallowStructProviderSyntax()) {
-          throw new EvalException(
-              loc,
+          throw Starlark.errorf(
               "Returning a struct from a rule implementation function is deprecated and will "
                   + "be removed soon. It may be temporarily re-enabled by setting "
                   + "--incompatible_disallow_struct_provider_syntax=false . See "
@@ -345,17 +359,11 @@
         StructImpl struct = (StructImpl) target;
         oldStyleProviders = struct;
 
-        if (struct.getValue("providers") != null) {
-          Iterable<?> iterable = cast("providers", struct, Iterable.class, loc);
-          for (Object o : iterable) {
-            Info declaredProvider =
-                SkylarkType.cast(
-                    o,
-                    Info.class,
-                    loc,
-                    "The value of 'providers' should be a sequence of declared providers");
-            Provider.Key providerKey = getProviderKey(loc, declaredProvider);
-            if (declaredProviders.put(providerKey, declaredProvider) != null) {
+        Object providersField = struct.getValue("providers");
+        if (providersField != null) {
+          for (Info provider : Sequence.cast(providersField, Info.class, "providers")) {
+            Provider.Key providerKey = getProviderKey(loc, provider);
+            if (declaredProviders.put(providerKey, provider) != null) {
               context
                   .getRuleContext()
                   .ruleError("Multiple conflicting returned providers with key " + providerKey);
@@ -367,18 +375,12 @@
         // Single declared provider
         declaredProviders.put(providerKey, info);
       }
-    } else if (target instanceof Iterable) {
+    } else if (target instanceof Sequence) {
       // Sequence of declared providers
-      for (Object o : (Iterable) target) {
-        Info declaredProvider =
-            SkylarkType.cast(
-                o,
-                Info.class,
-                loc,
-                "A return value of a rule implementation function should be "
-                    + "a sequence of declared providers");
-        Provider.Key providerKey = getProviderKey(loc, declaredProvider);
-        if (declaredProviders.put(providerKey, declaredProvider)  != null) {
+      for (Info provider :
+          Sequence.cast(target, Info.class, "result of rule implementation function")) {
+        Provider.Key providerKey = getProviderKey(loc, provider);
+        if (declaredProviders.put(providerKey, provider) != null) {
           context
               .getRuleContext()
               .ruleError("Multiple conflicting returned providers with key " + providerKey);
@@ -416,8 +418,10 @@
       } else if (field.equals("output_groups")) {
         addOutputGroups(oldStyleProviders.getValue(field), builder);
       } else if (field.equals("instrumented_files")) {
-        StructImpl insStruct = cast("instrumented_files", oldStyleProviders, StructImpl.class, loc);
-        addInstrumentedFiles(insStruct, context.getRuleContext(), builder);
+        addInstrumentedFiles(
+            oldStyleProviders.getValue("instrumented_files", StructImpl.class),
+            context.getRuleContext(),
+            builder);
       } else if (!field.equals("providers")) { // "providers" already handled above.
         addProviderFromLegacySyntax(
             builder, oldStyleProviders, field, oldStyleProviders.getValue(field));
@@ -536,15 +540,26 @@
       // TODO(cparsons): Look into deprecating this option.
       for (String field : provider.getFieldNames()) {
         if (field.equals("files")) {
-          files = cast("files", provider, Depset.class, Artifact.class, loc);
+          Object x = provider.getValue("files");
+          // TODO(adonovan): factor with Depset.getSetFromParam and simplify.
+          try {
+            files = (Depset) x; // (ClassCastException)
+            files.getSet(Artifact.class); // (TypeException)
+          } catch (@SuppressWarnings("UnusedException")
+              ClassCastException
+              | Depset.TypeException ex) {
+            throw Starlark.errorf(
+                "expected depset of Files for 'files' but got %s instead",
+                EvalUtils.getDataTypeName(x, true));
+          }
         } else if (field.equals("runfiles")) {
-          statelessRunfiles = cast("runfiles", provider, Runfiles.class, loc);
+          statelessRunfiles = provider.getValue("runfiles", Runfiles.class);
         } else if (field.equals("data_runfiles")) {
-          dataRunfiles = cast("data_runfiles", provider, Runfiles.class, loc);
+          dataRunfiles = provider.getValue("data_runfiles", Runfiles.class);
         } else if (field.equals("default_runfiles")) {
-          defaultRunfiles = cast("default_runfiles", provider, Runfiles.class, loc);
+          defaultRunfiles = provider.getValue("default_runfiles", Runfiles.class);
         } else if (field.equals("executable") && provider.getValue("executable") != null) {
-          executable = cast("executable", provider, Artifact.class, loc);
+          executable = provider.getValue("executable", Artifact.class);
         }
       }
 
@@ -701,24 +716,6 @@
     }
   }
 
-  private static <T> T cast(String paramName, ClassObject struct, Class<T> expectedGenericType,
-      Class<?> expectedArgumentType, Location loc) throws EvalException {
-    Object value = struct.getValue(paramName);
-    return SkylarkType.cast(value, expectedGenericType, expectedArgumentType, loc,
-        "expected %s for '%s' but got %s instead: %s",
-        SkylarkType.of(expectedGenericType, expectedArgumentType),
-        paramName, EvalUtils.getDataTypeName(value, true), value);
-  }
-
-  private static <T> T cast(String paramName, ClassObject struct, Class<T> expectedType,
-      Location loc) throws EvalException {
-    Object value = struct.getValue(paramName);
-    return SkylarkType.cast(value, expectedType, loc,
-        "expected %s for '%s' but got %s instead: %s",
-        SkylarkType.of(expectedType),
-        paramName, EvalUtils.getDataTypeName(value, false), value);
-  }
-
   private static Runfiles mergeFiles(
       Runfiles runfiles, Artifact executable, RuleContext ruleContext) {
     if (executable == null) {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java
index 0d05107..33e45fc 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkRuleContext.java
@@ -723,7 +723,7 @@
     checkMutable("expand");
     try {
       Map<Label, Iterable<Artifact>> labelMap = new HashMap<>();
-      for (Artifact artifact : artifacts.getContents(Artifact.class, "artifacts")) {
+      for (Artifact artifact : Sequence.cast(artifacts, Artifact.class, "artifacts")) {
         labelMap.put(artifactsLabelMap.get(artifact), ImmutableList.of(artifact));
       }
       return LabelExpander.expand(expression, labelMap, labelResolver);
@@ -802,7 +802,8 @@
     checkMutable("check_placeholders");
     List<String> actualPlaceHolders = new LinkedList<>();
     Set<String> allowedPlaceholderSet =
-        ImmutableSet.copyOf(allowedPlaceholders.getContents(String.class, "allowed_placeholders"));
+        ImmutableSet.copyOf(
+            Sequence.cast(allowedPlaceholders, String.class, "allowed_placeholders"));
     ImplicitOutputsFunction.createPlaceholderSubstitutionFormatString(template, actualPlaceHolders);
     for (String placeholder : actualPlaceHolders) {
       if (!allowedPlaceholderSet.contains(placeholder)) {
@@ -818,7 +819,7 @@
       throws EvalException {
     checkMutable("expand_make_variables");
     final Map<String, String> additionalSubstitutionsMap =
-        additionalSubstitutions.getContents(String.class, String.class, "additional_substitutions");
+        Dict.cast(additionalSubstitutions, String.class, String.class, "additional_substitutions");
     return expandMakeVariables(attributeName, command, additionalSubstitutionsMap);
   }
 
@@ -936,7 +937,7 @@
     try {
       return LocationExpander.withExecPaths(
               getRuleContext(),
-              makeLabelMap(targets.getContents(TransitiveInfoCollection.class, "targets")))
+              makeLabelMap(Sequence.cast(targets, TransitiveInfoCollection.class, "targets")))
           .expand(input);
     } catch (IllegalStateException ise) {
       throw new EvalException(null, ise);
@@ -997,7 +998,7 @@
       builder.addRunfiles(getRuleContext(), RunfilesProvider.DEFAULT_RUNFILES);
     }
     if (!files.isEmpty()) {
-      builder.addArtifacts(files.getContents(Artifact.class, "files"));
+      builder.addArtifacts(Sequence.cast(files, Artifact.class, "files"));
     }
     if (transitiveFiles != Starlark.NONE) {
       builder.addTransitiveArtifacts(
@@ -1007,14 +1008,14 @@
       // If Starlark code directly manipulates symlinks, activate more stringent validity checking.
       checkConflicts = true;
       for (Map.Entry<String, Artifact> entry :
-          symlinks.getContents(String.class, Artifact.class, "symlinks").entrySet()) {
+          Dict.cast(symlinks, String.class, Artifact.class, "symlinks").entrySet()) {
         builder.addSymlink(PathFragment.create(entry.getKey()), entry.getValue());
       }
     }
     if (!rootSymlinks.isEmpty()) {
       checkConflicts = true;
       for (Map.Entry<String, Artifact> entry :
-          rootSymlinks.getContents(String.class, Artifact.class, "root_symlinks").entrySet()) {
+          Dict.cast(rootSymlinks, String.class, Artifact.class, "root_symlinks").entrySet()) {
         builder.addRootSymlink(PathFragment.create(entry.getKey()), entry.getValue());
       }
     }
@@ -1042,7 +1043,7 @@
     // The best way to fix this probably is to convert CommandHelper to Starlark.
     CommandHelper helper =
         CommandHelper.builder(getRuleContext())
-            .addToolDependencies(tools.getContents(TransitiveInfoCollection.class, "tools"))
+            .addToolDependencies(Sequence.cast(tools, TransitiveInfoCollection.class, "tools"))
             .addLabelMap(labelDict)
             .build();
     String attribute = Type.STRING.convertOptional(attributeUnchecked, "attribute", ruleLabel);
@@ -1063,7 +1064,7 @@
 
     ImmutableMap<String, String> executionRequirements =
         ImmutableMap.copyOf(
-            Dict.castSkylarkDictOrNoneToDict(
+            Dict.noneableCast(
                 executionRequirementsUnchecked,
                 String.class,
                 String.class,
@@ -1089,7 +1090,7 @@
     checkMutable("resolve_tools");
     CommandHelper helper =
         CommandHelper.builder(getRuleContext())
-            .addToolDependencies(tools.getContents(TransitiveInfoCollection.class, "tools"))
+            .addToolDependencies(Sequence.cast(tools, TransitiveInfoCollection.class, "tools"))
             .build();
     return Tuple.<Object>of(
         Depset.of(Artifact.TYPE, helper.getResolvedTools()), helper.getToolsRunfilesSuppliers());
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageCommon.java b/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageCommon.java
index ecd1252..d42cfab 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/CoverageCommon.java
@@ -36,7 +36,6 @@
 public class CoverageCommon implements CoverageCommonApi<ConstraintValueInfo, SkylarkRuleContext> {
 
   @Override
-  @SuppressWarnings("unchecked") // Casting extensions param is verified by Starlark interpreter.
   public InstrumentedFilesInfoApi instrumentedFilesInfo(
       SkylarkRuleContext skylarkRuleContext,
       Sequence<?> sourceAttributes, // <String>
@@ -44,14 +43,12 @@
       Object extensions)
       throws EvalException {
     List<String> extensionsList =
-        extensions == Starlark.NONE
-            ? null
-            : Sequence.castList((List<?>) extensions, String.class, "extensions");
+        extensions == Starlark.NONE ? null : Sequence.cast(extensions, String.class, "extensions");
 
     return createInstrumentedFilesInfo(
         skylarkRuleContext.getRuleContext(),
-        sourceAttributes.getContents(String.class, "source_attributes"),
-        dependencyAttributes.getContents(String.class, "dependency_attributes"),
+        Sequence.cast(sourceAttributes, String.class, "source_attributes"),
+        Sequence.cast(dependencyAttributes, String.class, "dependency_attributes"),
         extensionsList);
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java
index 0762485..705e09e 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java
@@ -315,7 +315,7 @@
     SkylarkPath p = getPath("template()", path);
     SkylarkPath t = getPath("template()", template);
     Map<String, String> substitutionMap =
-        substitutions.getContents(String.class, String.class, "substitutions");
+        Dict.cast(substitutions, String.class, String.class, "substitutions");
     WorkspaceRuleEvent w =
         WorkspaceRuleEvent.newTemplateEvent(
             p.toString(),
@@ -407,13 +407,10 @@
     return featureEnabled && isRemotable() && remoteExecEnabled;
   }
 
-  @SuppressWarnings("unchecked")
   private ImmutableMap<String, String> getExecProperties() throws EvalException {
-    Dict<String, String> execPropertiesDict =
-        (Dict<String, String>) getAttr().getValue("exec_properties", Dict.class);
-    Map<String, String> execPropertiesMap =
-        execPropertiesDict.getContents(String.class, String.class, "exec_properties");
-    return ImmutableMap.copyOf(execPropertiesMap);
+    return ImmutableMap.copyOf(
+        Dict.cast(
+            getAttr().getValue("exec_properties"), String.class, String.class, "exec_properties"));
   }
 
   private Map.Entry<PathFragment, Path> getRemotePathFromLabel(Label label)
@@ -502,7 +499,7 @@
     validateExecuteArguments(arguments);
 
     Map<String, String> environment =
-        uncheckedEnvironment.getContents(String.class, String.class, "environment");
+        Dict.cast(uncheckedEnvironment, String.class, String.class, "environment");
 
     if (canExecuteRemote()) {
       try (SilentCloseable c =
@@ -652,17 +649,12 @@
     reportProgress("Will fail after download of " + url + ". " + errorMessage);
   }
 
-  @SuppressWarnings({"unchecked", "rawtypes"}) // Explained in method comment
-  private static Map<String, Dict<?, ?>> getAuthContents(
-      Dict<?, ?> authUnchecked, @Nullable String description) throws EvalException {
-    // This method would not be worth having (Dict#getContents could be called
-    // instead), except that some trickery is required to cast Map<String, Dict> to
-    // Map<String, Dict<?, ?>>.
-
-    // getContents can only guarantee raw types, so Dict is the raw type here.
-    Map<String, Dict> result = authUnchecked.getContents(String.class, Dict.class, description);
-
-    return (Map<String, Dict<?, ?>>) (Map<String, ? extends Dict>) result;
+  private static Map<String, Dict<?, ?>> getAuthContents(Dict<?, ?> x, String what)
+      throws EvalException {
+    // Dict.cast returns Dict<String, raw Dict>.
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    Map<String, Dict<?, ?>> res = (Map) Dict.cast(x, String.class, Dict.class, what);
+    return res;
   }
 
   @Override
@@ -673,7 +665,7 @@
       Boolean executable,
       Boolean allowFail,
       String canonicalId,
-      Dict<?, ?> authUnchecked, // <String, Dict<?, ?>> expected
+      Dict<?, ?> authUnchecked, // <String, Dict> expected
       String integrity,
       StarlarkThread thread)
       throws RepositoryFunctionException, EvalException, InterruptedException {
@@ -789,7 +781,7 @@
       String stripPrefix,
       Boolean allowFail,
       String canonicalId,
-      Dict<?, ?> auth, // <String, Dict<?, ?>> expected
+      Dict<?, ?> auth, // <String, Dict> expected
       String integrity,
       StarlarkThread thread)
       throws RepositoryFunctionException, InterruptedException, EvalException {
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryModule.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryModule.java
index d76ac93..ee33c55 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryModule.java
@@ -18,7 +18,6 @@
 import static com.google.devtools.build.lib.packages.Type.BOOLEAN;
 import static com.google.devtools.build.lib.packages.Type.STRING;
 import static com.google.devtools.build.lib.packages.Type.STRING_LIST;
-import static com.google.devtools.build.lib.syntax.SkylarkType.castMap;
 
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.analysis.BaseRuleClasses;
@@ -88,7 +87,7 @@
     builder.add(attr("expect_failure", STRING));
     if (attrs != Starlark.NONE) {
       for (Map.Entry<String, Descriptor> attr :
-          castMap(attrs, String.class, Descriptor.class, "attrs").entrySet()) {
+          Dict.cast(attrs, String.class, Descriptor.class, "attrs").entrySet()) {
         Descriptor attrDescriptor = attr.getValue();
         AttributeValueSource source = attrDescriptor.getValueSource();
         String attrName = source.convertToNativeName(attr.getKey());
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCcModule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCcModule.java
index ea93db3..94b3962 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCcModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BazelCcModule.java
@@ -109,7 +109,7 @@
         /* grepIncludes= */ null,
         /* headersForClifDoNotUseThisParam= */ ImmutableList.of(),
         StarlarkList.immutableCopyOf(
-            additionalInputs.getContents(Artifact.class, "additional_inputs")),
+            Sequence.cast(additionalInputs, Artifact.class, "additional_inputs")),
         thread);
   }
 
@@ -158,7 +158,7 @@
       throws EvalException {
     CcCompilationOutputs.Builder ccCompilationOutputsBuilder = CcCompilationOutputs.builder();
     for (CcCompilationOutputs ccCompilationOutputs :
-        compilationOutputs.getContents(CcCompilationOutputs.class, "compilation_outputs")) {
+        Sequence.cast(compilationOutputs, CcCompilationOutputs.class, "compilation_outputs")) {
       ccCompilationOutputsBuilder.merge(ccCompilationOutputs);
     }
     return ccCompilationOutputsBuilder.build();
diff --git a/src/main/java/com/google/devtools/build/lib/packages/ImplicitOutputsFunction.java b/src/main/java/com/google/devtools/build/lib/packages/ImplicitOutputsFunction.java
index c8dd28a..a1d6576 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/ImplicitOutputsFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/ImplicitOutputsFunction.java
@@ -13,7 +13,6 @@
 // limitations under the License.
 package com.google.devtools.build.lib.packages;
 
-import static com.google.devtools.build.lib.syntax.SkylarkType.castMap;
 import static java.util.Collections.singleton;
 import static java.util.stream.Collectors.toCollection;
 
@@ -31,6 +30,7 @@
 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.syntax.ClassObject;
+import com.google.devtools.build.lib.syntax.Dict;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.Location;
 import com.google.devtools.build.lib.syntax.Starlark;
@@ -112,7 +112,7 @@
       try {
         ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
         for (Map.Entry<String, String> entry :
-            castMap(
+            Dict.cast(
                     callback.call(eventHandler, attrs),
                     String.class,
                     String.class,
diff --git a/src/main/java/com/google/devtools/build/lib/packages/StructImpl.java b/src/main/java/com/google/devtools/build/lib/packages/StructImpl.java
index 762c0ee..26bd781 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/StructImpl.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/StructImpl.java
@@ -24,7 +24,6 @@
 import com.google.devtools.build.lib.syntax.Location;
 import com.google.devtools.build.lib.syntax.Printer;
 import com.google.devtools.build.lib.syntax.Sequence;
-import com.google.devtools.build.lib.syntax.SkylarkType;
 import com.google.devtools.build.lib.syntax.Starlark;
 import com.google.protobuf.TextFormat;
 import java.util.ArrayList;
@@ -83,8 +82,15 @@
     if (obj == null) {
       return null;
     }
-    SkylarkType.checkType(obj, type, key);
-    return type.cast(obj);
+    try {
+      return type.cast(obj);
+    } catch (
+        @SuppressWarnings("UnusedException")
+        ClassCastException unused) {
+      throw Starlark.errorf(
+          "for %s field, got %s, want %s",
+          key, Starlark.type(obj), EvalUtils.getDataTypeNameFromClass(type));
+    }
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/packages/TargetUtils.java b/src/main/java/com/google/devtools/build/lib/packages/TargetUtils.java
index 302c856..7bc58b0 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/TargetUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/TargetUtils.java
@@ -275,15 +275,17 @@
    *     SkylarkSematicOptions#experimentalAllowTagsPropagation}
    */
   public static ImmutableMap<String, String> getFilteredExecutionInfo(
-      Object executionRequirementsUnchecked, Rule rule, boolean allowTagsPropagation)
+      @Nullable Object executionRequirementsUnchecked, Rule rule, boolean allowTagsPropagation)
       throws EvalException {
     Map<String, String> checkedExecutionRequirements =
         TargetUtils.filter(
-            Dict.castSkylarkDictOrNoneToDict(
-                executionRequirementsUnchecked,
-                String.class,
-                String.class,
-                "execution_requirements"));
+            executionRequirementsUnchecked == null
+                ? ImmutableMap.of()
+                : Dict.noneableCast(
+                    executionRequirementsUnchecked,
+                    String.class,
+                    String.class,
+                    "execution_requirements"));
 
     Map<String, String> executionInfoBuilder = new HashMap<>();
     // adding filtered execution requirements to the execution info map
diff --git a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactoryHelper.java b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactoryHelper.java
index 412ea2d..4c51fb4 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactoryHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceFactoryHelper.java
@@ -25,8 +25,8 @@
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.events.StoredEventHandler;
 import com.google.devtools.build.lib.packages.RuleFactory.BuildLangTypedAttributeValuesMap;
+import com.google.devtools.build.lib.syntax.Dict;
 import com.google.devtools.build.lib.syntax.EvalException;
-import com.google.devtools.build.lib.syntax.SkylarkType;
 import com.google.devtools.build.lib.syntax.StarlarkSemantics;
 import com.google.devtools.build.lib.syntax.StarlarkThread;
 import java.util.Map;
@@ -121,9 +121,8 @@
       throws EvalException, LabelSyntaxException {
     Object repoMapping = kwargs.get("repo_mapping");
     if (repoMapping != null) {
-      Map<String, String> map =
-          SkylarkType.castMap(repoMapping, String.class, String.class, "repo_mapping");
-      for (Map.Entry<String, String> e : map.entrySet()) {
+      for (Map.Entry<String, String> e :
+          Dict.cast(repoMapping, String.class, String.class, "repo_mapping").entrySet()) {
         // Create repository names with validation; may throw LabelSyntaxException.
         builder.addRepositoryMappingEntry(
             RepositoryName.create("@" + externalRepoName),
diff --git a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceGlobals.java b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceGlobals.java
index 9a5ae1c..d363b94 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/WorkspaceGlobals.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/WorkspaceGlobals.java
@@ -106,14 +106,14 @@
     builder.addRepositoryMappingEntry(
         RepositoryName.MAIN, RepositoryName.createFromValidStrippedName(name), RepositoryName.MAIN);
     parseManagedDirectories(
-        managedDirectories.getContents(String.class, Object.class, "managed_directories"));
+        Dict.cast(managedDirectories, String.class, Object.class, "managed_directories"));
     return NONE;
   }
 
   @Override
   public NoneType dontSymlinkDirectoriesInExecroot(Sequence<?> paths, StarlarkThread thread)
       throws EvalException, InterruptedException {
-    List<String> pathsList = paths.getContents(String.class, "paths");
+    List<String> pathsList = Sequence.cast(paths, String.class, "paths");
     Set<String> set = Sets.newHashSet();
     for (String path : pathsList) {
       PathFragment pathFragment = PathFragment.create(path);
@@ -250,7 +250,7 @@
       throws EvalException, InterruptedException {
     // Add to the package definition for later.
     Package.Builder builder = PackageFactory.getContext(thread).pkgBuilder;
-    List<String> patterns = platformLabels.getContents(String.class, "platform_labels");
+    List<String> patterns = Sequence.cast(platformLabels, String.class, "platform_labels");
     builder.addRegisteredExecutionPlatforms(renamePatterns(patterns, builder, thread));
     return NONE;
   }
@@ -260,7 +260,7 @@
       throws EvalException, InterruptedException {
     // Add to the package definition for later.
     Package.Builder builder = PackageFactory.getContext(thread).pkgBuilder;
-    List<String> patterns = toolchainLabels.getContents(String.class, "toolchain_labels");
+    List<String> patterns = Sequence.cast(toolchainLabels, String.class, "toolchain_labels");
     builder.addRegisteredToolchains(renamePatterns(patterns, builder, thread));
     return NONE;
   }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidFeatureFlagSetProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidFeatureFlagSetProvider.java
index 937f9b4..69a8f25 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidFeatureFlagSetProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidFeatureFlagSetProvider.java
@@ -160,7 +160,7 @@
     public AndroidFeatureFlagSetProvider create(Dict<?, ?> flags) // <Label, String>
         throws EvalException {
       return new AndroidFeatureFlagSetProvider(
-          Optional.of(Dict.castSkylarkDictOrNoneToDict(flags, Label.class, String.class, "flags")));
+          Optional.of(Dict.noneableCast(flags, Label.class, String.class, "flags")));
     }
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdeInfoProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdeInfoProvider.java
index 343a9fc..d9eeb4e 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdeInfoProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidIdeInfoProvider.java
@@ -335,7 +335,7 @@
         Dict<?, ?> nativeLibs) // <String, Depset>
         throws EvalException {
       Map<String, Depset> nativeLibsMap =
-          nativeLibs.getContents(String.class, Depset.class, "native_libs");
+          Dict.cast(nativeLibs, String.class, Depset.class, "native_libs");
 
       ImmutableMap.Builder<String, NestedSet<Artifact>> builder = ImmutableMap.builder();
       for (Map.Entry<String, Depset> entry : nativeLibsMap.entrySet()) {
@@ -353,10 +353,10 @@
           fromNoneable(resourceJar, OutputJar.class),
           definesAndroidResources,
           fromNoneable(aar, Artifact.class),
-          ImmutableList.copyOf(idlSrcs.getContents(Artifact.class, "idl_srcs")),
+          ImmutableList.copyOf(Sequence.cast(idlSrcs, Artifact.class, "idl_srcs")),
           ImmutableList.copyOf(
-              idlGeneratedJavaFiles.getContents(Artifact.class, "idl_generated_java_files")),
-          ImmutableList.copyOf(apksUnderTest.getContents(Artifact.class, "apks_under_test")),
+              Sequence.cast(idlGeneratedJavaFiles, Artifact.class, "idl_generated_java_files")),
+          ImmutableList.copyOf(Sequence.cast(apksUnderTest, Artifact.class, "apks_under_test")),
           builder.build(),
           fromNoneable(resourceApk, Artifact.class));
     }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidProguardInfo.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidProguardInfo.java
index 355a028..49af24d 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidProguardInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidProguardInfo.java
@@ -59,7 +59,7 @@
         throws EvalException {
       return new AndroidProguardInfo(
           ImmutableList.copyOf(
-              localProguardSpecs.getContents(Artifact.class, "local_proguard_specs")));
+              Sequence.cast(localProguardSpecs, Artifact.class, "local_proguard_specs")));
     }
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSkylarkData.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSkylarkData.java
index f226df1..ef74036 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSkylarkData.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSkylarkData.java
@@ -80,7 +80,7 @@
     // We assume this is an analysis-phase thread.
     Label label = BazelStarlarkContext.from(thread).getAnalysisRuleLabel();
     return AssetDependencies.fromProviders(
-            deps.getContents(AndroidAssetsInfo.class, "deps"), neverlink)
+            Sequence.cast(deps, AndroidAssetsInfo.class, "deps"), neverlink)
         .toInfo(label);
   }
 
@@ -98,10 +98,10 @@
               ctx,
               DataBinding.getDisabledDataBindingContext(ctx),
               ResourceDependencies.fromProviders(
-                  deps.getContents(AndroidResourcesInfo.class, "deps"),
+                  Sequence.cast(deps, AndroidResourcesInfo.class, "deps"),
                   /* neverlink = */ neverlink),
               AssetDependencies.fromProviders(
-                  assets.getContents(AndroidAssetsInfo.class, "assets"),
+                  Sequence.cast(assets, AndroidAssetsInfo.class, "assets"),
                   /* neverlink = */ neverlink),
               StampedAndroidManifest.createEmpty(
                   ctx.getActionConstructionContext(), customPackage, /* exported = */ false))
@@ -140,12 +140,12 @@
     try {
       return AndroidAssets.from(
               errorReporter,
-              listFromNoneable(assets, ConfiguredTarget.class),
-              isNone(assetsDir) ? null : PathFragment.create(fromNoneable(assetsDir, String.class)))
+              isNone(assets) ? null : Sequence.cast(assets, ConfiguredTarget.class, "assets"),
+              isNone(assetsDir) ? null : PathFragment.create((String) assetsDir))
           .process(
               ctx,
               AssetDependencies.fromProviders(
-                  deps.getContents(AndroidAssetsInfo.class, "deps"), neverlink))
+                  Sequence.cast(deps, AndroidAssetsInfo.class, "deps"), neverlink))
           .toProvider();
     } catch (RuleErrorException e) {
       throw handleRuleException(errorReporter, e);
@@ -165,13 +165,13 @@
     try {
       return AndroidResources.from(
               errorReporter,
-              getFileProviders(resources.getContents(ConfiguredTarget.class, "resources")),
+              getFileProviders(Sequence.cast(resources, ConfiguredTarget.class, "resources")),
               "resources")
           .process(
               ctx,
               manifest.asStampedManifest(),
               ResourceDependencies.fromProviders(
-                  deps.getContents(AndroidResourcesInfo.class, "deps"), neverlink),
+                  Sequence.cast(deps, AndroidResourcesInfo.class, "deps"), neverlink),
               DataBinding.contextFrom(
                   enableDataBinding, ctx.getActionConstructionContext(), ctx.getAndroidConfig()));
     } catch (RuleErrorException e) {
@@ -250,8 +250,9 @@
             resourcesInfo.getRTxt(),
             libraryClassJar,
             ImmutableList.copyOf(
-                localProguardSpecs.getContents(Artifact.class, "local_proguard_specs")))
-        .toProvider(deps.getContents(AndroidLibraryAarInfo.class, "deps"), definesLocalResources);
+                Sequence.cast(localProguardSpecs, Artifact.class, "local_proguard_specs")))
+        .toProvider(
+            Sequence.cast(deps, AndroidLibraryAarInfo.class, "deps"), definesLocalResources);
   }
 
   @Override
@@ -262,7 +263,7 @@
       Artifact androidManifestArtifact,
       Sequence<?> deps) // <ConfiguredTarget>
       throws InterruptedException, EvalException {
-    List<ConfiguredTarget> depsTargets = deps.getContents(ConfiguredTarget.class, "deps");
+    List<ConfiguredTarget> depsTargets = Sequence.cast(deps, ConfiguredTarget.class, "deps");
 
     ValidatedAndroidResources validatedResources =
         AndroidResources.forAarImport(resources)
@@ -303,7 +304,7 @@
       Sequence<?> densities) // <String>)
       throws InterruptedException, EvalException {
     SkylarkErrorReporter errorReporter = SkylarkErrorReporter.from(ctx.getRuleErrorConsumer());
-    List<ConfiguredTarget> depsTargets = deps.getContents(ConfiguredTarget.class, "deps");
+    List<ConfiguredTarget> depsTargets = Sequence.cast(deps, ConfiguredTarget.class, "deps");
 
     try {
       AndroidManifest rawManifest =
@@ -323,11 +324,12 @@
               rawManifest,
               AndroidResources.from(
                   errorReporter,
-                  getFileProviders(resources.getContents(ConfiguredTarget.class, "resource_files")),
+                  getFileProviders(
+                      Sequence.cast(resources, ConfiguredTarget.class, "resource_files")),
                   "resource_files"),
               AndroidAssets.from(
                   errorReporter,
-                  listFromNoneable(assets, ConfiguredTarget.class),
+                  isNone(assets) ? null : Sequence.cast(assets, ConfiguredTarget.class, "assets"),
                   isNone(assetsDir)
                       ? null
                       : PathFragment.create(fromNoneable(assetsDir, String.class))),
@@ -336,12 +338,12 @@
                   /* neverlink = */ false),
               AssetDependencies.fromProviders(
                   getProviders(depsTargets, AndroidAssetsInfo.PROVIDER), /* neverlink = */ false),
-              manifestValues.getContents(String.class, String.class, "manifest_values"),
-              noCompressExtensions.getContents(String.class, "nocompress_extensions"),
+              Dict.cast(manifestValues, String.class, String.class, "manifest_values"),
+              Sequence.cast(noCompressExtensions, String.class, "nocompress_extensions"),
               ResourceFilterFactory.from(
-                  resourceConfigurationFilters.getContents(
-                      String.class, "resource_configuration_filters"),
-                  densities.getContents(String.class, "densities")));
+                  Sequence.cast(
+                      resourceConfigurationFilters, String.class, "resource_configuration_filters"),
+                  Sequence.cast(densities, String.class, "densities")));
 
       ImmutableMap.Builder<Provider, NativeInfo> builder = ImmutableMap.builder();
       builder.putAll(getNativeInfosFrom(resourceApk, ctx.getLabel()));
@@ -380,11 +382,11 @@
         fromNoneableOrDefault(
             shrinkResources, Boolean.class, ctx.getAndroidConfig().useAndroidResourceShrinking()),
         ResourceFilterFactory.from(
-            resourceConfigurationFilters.getContents(
-                String.class, "resource_configuration_filters"),
-            densities.getContents(String.class, "densities")),
+            Sequence.cast(
+                resourceConfigurationFilters, String.class, "resource_configuration_filters"),
+            Sequence.cast(densities, String.class, "densities")),
         ImmutableList.copyOf(
-            noCompressExtensions.getContents(String.class, "nocompress_extensions")));
+            Sequence.cast(noCompressExtensions, String.class, "nocompress_extensions")));
   }
 
   @Override
@@ -433,9 +435,9 @@
       boolean dataBindingEnabled)
       throws InterruptedException, EvalException {
     SkylarkErrorReporter errorReporter = SkylarkErrorReporter.from(ctx.getRuleErrorConsumer());
-    List<ConfiguredTarget> depsTargets = deps.getContents(ConfiguredTarget.class, "deps");
+    List<ConfiguredTarget> depsTargets = Sequence.cast(deps, ConfiguredTarget.class, "deps");
     Map<String, String> manifestValueMap =
-        manifestValues.getContents(String.class, String.class, "manifest_values");
+        Dict.cast(manifestValues, String.class, String.class, "manifest_values");
 
     try {
       BinaryDataSettings settings =
@@ -475,14 +477,14 @@
                   AndroidResources.from(
                       errorReporter,
                       getFileProviders(
-                          resources.getContents(ConfiguredTarget.class, "resource_files")),
+                          Sequence.cast(resources, ConfiguredTarget.class, "resource_files")),
                       "resource_files"),
                   AndroidAssets.from(
                       errorReporter,
-                      listFromNoneable(assets, ConfiguredTarget.class),
-                      isNone(assetsDir)
+                      isNone(assets)
                           ? null
-                          : PathFragment.create(fromNoneable(assetsDir, String.class))),
+                          : Sequence.cast(assets, ConfiguredTarget.class, "assets"),
+                      isNone(assetsDir) ? null : PathFragment.create((String) assetsDir)),
                   resourceDeps,
                   AssetDependencies.fromProviders(
                       getProviders(depsTargets, AndroidAssetsInfo.PROVIDER),
@@ -522,7 +524,7 @@
     BinaryDataSettings settings =
         fromNoneableOrDefault(
             maybeSettings, BinaryDataSettings.class, defaultBinaryDataSettings(ctx));
-    List<ConfiguredTarget> depsTargets = deps.getContents(ConfiguredTarget.class, "deps");
+    List<ConfiguredTarget> depsTargets = Sequence.cast(deps, ConfiguredTarget.class, "deps");
 
     if (!settings.shrinkResources) {
       return binaryDataInfo;
@@ -535,9 +537,9 @@
             binaryDataInfo.getResourceProguardConfig(),
             binaryDataInfo.getManifestInfo().getManifest(),
             filesFromConfiguredTargets(
-                localProguardSpecs.getContents(ConfiguredTarget.class, "proguard_specs")),
+                Sequence.cast(localProguardSpecs, ConfiguredTarget.class, "proguard_specs")),
             filesFromConfiguredTargets(
-                extraProguardSpecs.getContents(ConfiguredTarget.class, "extra_proguard_specs")),
+                Sequence.cast(extraProguardSpecs, ConfiguredTarget.class, "extra_proguard_specs")),
             getProviders(depsTargets, ProguardSpecProvider.PROVIDER));
 
     // TODO(asteinb): There should never be more than one direct resource exposed in the provider.
@@ -646,22 +648,6 @@
     return value;
   }
 
-  /**
-   * Converts a "Noneable" Object passed by Starlark to a List of the appropriate type.
-   *
-   * <p>This first calls {@link #fromNoneable(Object, Class)} to get a Sequence<?>, then safely
-   * casts it to a list with the appropriate generic.
-   */
-  @Nullable
-  public static <T> List<T> listFromNoneable(Object object, Class<T> clazz) throws EvalException {
-    Sequence<?> asList = fromNoneable(object, Sequence.class);
-    if (asList == null) {
-      return null;
-    }
-
-    return Sequence.castList(asList, clazz, null);
-  }
-
   private static ImmutableList<Artifact> filesFromConfiguredTargets(
       List<ConfiguredTarget> targets) {
     ImmutableList.Builder<Artifact> builder = ImmutableList.builder();
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingV2Provider.java b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingV2Provider.java
index c28bb4c..f5a0ca2 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingV2Provider.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/DataBindingV2Provider.java
@@ -193,13 +193,17 @@
           databindingV2ProvidersInDeps == null
               ? null
               : ImmutableList.copyOf(
-                  databindingV2ProvidersInDeps.getContents(
-                      DataBindingV2Provider.class, "databinding_v2_providers_in_deps")),
+                  Sequence.cast(
+                      databindingV2ProvidersInDeps,
+                      DataBindingV2Provider.class,
+                      "databinding_v2_providers_in_deps")),
           databindingV2ProvidersInExports == null
               ? null
               : ImmutableList.copyOf(
-                  databindingV2ProvidersInExports.getContents(
-                      DataBindingV2Provider.class, "databinding_v2_providers_in_exports")));
+                  Sequence.cast(
+                      databindingV2ProvidersInExports,
+                      DataBindingV2Provider.class,
+                      "databinding_v2_providers_in_exports")));
     }
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/UsesDataBindingProvider.java b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/UsesDataBindingProvider.java
index f90d502..c54c9a3 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/databinding/UsesDataBindingProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/databinding/UsesDataBindingProvider.java
@@ -56,7 +56,7 @@
     public UsesDataBindingProvider createInfo(Sequence<?> metadataOutputs) // <Artifact>
         throws EvalException {
       return new UsesDataBindingProvider(
-          metadataOutputs.getContents(Artifact.class, "metadata_outputs"));
+          Sequence.cast(metadataOutputs, Artifact.class, "metadata_outputs"));
     }
   }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/config/ConfigGlobalLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/config/ConfigGlobalLibrary.java
index 2b0d099..30b9926 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/config/ConfigGlobalLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/config/ConfigGlobalLibrary.java
@@ -48,8 +48,8 @@
       StarlarkThread thread)
       throws EvalException {
     StarlarkSemantics semantics = thread.getSemantics();
-    List<String> inputsList = inputs.getContents(String.class, "inputs");
-    List<String> outputsList = outputs.getContents(String.class, "outputs");
+    List<String> inputsList = Sequence.cast(inputs, String.class, "inputs");
+    List<String> outputsList = Sequence.cast(outputs, String.class, "outputs");
     validateBuildSettingKeys(
         inputsList, Settings.INPUTS, semantics.experimentalStarlarkConfigTransitions());
     validateBuildSettingKeys(
@@ -64,7 +64,7 @@
       StarlarkThread thread)
       throws EvalException {
     Map<String, Object> changedSettingsMap =
-        changedSettings.getContents(String.class, Object.class, "changed_settings dict");
+        Dict.cast(changedSettings, String.class, Object.class, "changed_settings dict");
     validateBuildSettingKeys(changedSettingsMap.keySet(), Settings.OUTPUTS, true);
     return StarlarkDefinedConfigTransition.newAnalysisTestTransition(
         changedSettingsMap, thread.getCallerLocation());
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcBinary.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcBinary.java
index 6bf2eed..8681613 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcBinary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcBinary.java
@@ -1239,8 +1239,7 @@
           return null;
         }
         ImmutableList<String> exports =
-            ImmutableList.copyOf(
-                Sequence.castSkylarkListOrNoneToList(exportsField, String.class, "exports"));
+            ImmutableList.copyOf(Sequence.noneableCast(exportsField, String.class, "exports"));
 
         Object linkerInputField = ccSharedLibraryInfo.getValue("linker_input");
         if (linkerInputField == null) {
@@ -1262,7 +1261,7 @@
         }
         ImmutableList<String> linkOnceStaticLibs =
             ImmutableList.copyOf(
-                Sequence.castSkylarkListOrNoneToList(
+                Sequence.noneableCast(
                     linkOnceStaticLibsField, String.class, "link_once_static_libs"));
 
         directMergedCcSharedLibraryInfos.add(
@@ -1289,12 +1288,12 @@
 
         for (Tuple<Object> exportsAndLinkerInput : dynamicDeps.toList()) {
           List<String> exportsFromDynamicDep =
-              Sequence.castSkylarkListOrNoneToList(
+              Sequence.noneableCast(
                   exportsAndLinkerInput.get(0), String.class, "exports_from_dynamic_dep");
           CcLinkingContext.LinkerInput linkerInputFromDynamicDep =
               (CcLinkingContext.LinkerInput) exportsAndLinkerInput.get(1);
           List<String> linkOnceStaticLibsFromDynamicDep =
-              Sequence.castSkylarkListOrNoneToList(
+              Sequence.noneableCast(
                   exportsAndLinkerInput.get(0),
                   String.class,
                   "link_once_static_libs_from_dynamic_dep");
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
index 1fb4ede..e0de3d0 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcModule.java
@@ -135,7 +135,8 @@
       throws EvalException {
     SkylarkRuleContext ruleContext = nullIfNone(ruleContextOrNone, SkylarkRuleContext.class);
     ImmutableSet<String> unsupportedFeaturesSet =
-        ImmutableSet.copyOf(unsupportedFeatures.getContents(String.class, "unsupported_features"));
+        ImmutableSet.copyOf(
+            Sequence.cast(unsupportedFeatures, String.class, "unsupported_features"));
     final CppConfiguration cppConfiguration;
     final BuildOptions buildOptions;
     if (ruleContext == null) {
@@ -166,7 +167,8 @@
     }
     return FeatureConfigurationForStarlark.from(
         CcCommon.configureFeaturesOrThrowEvalException(
-            ImmutableSet.copyOf(requestedFeatures.getContents(String.class, "requested_features")),
+            ImmutableSet.copyOf(
+                Sequence.cast(requestedFeatures, String.class, "requested_features")),
             unsupportedFeaturesSet,
             toolchain,
             cppConfiguration),
@@ -594,7 +596,7 @@
 
   @Override
   public CcInfo mergeCcInfos(Sequence<?> ccInfos) throws EvalException {
-    return CcInfo.merge(ccInfos.getContents(CcInfo.class, /* description= */ null));
+    return CcInfo.merge(Sequence.cast(ccInfos, CcInfo.class, "infos"));
   }
 
   @Override
@@ -723,7 +725,7 @@
         Sequence<String> nonCodeInputs = nullIfNone(nonCodeInputsObject, Sequence.class);
         if (nonCodeInputs != null) {
           ccLinkingContextBuilder.addNonCodeInputs(
-              nonCodeInputs.getContents(Artifact.class, "additional_inputs"));
+              Sequence.cast(nonCodeInputs, Artifact.class, "additional_inputs"));
         }
         return ccLinkingContextBuilder.build();
       }
@@ -792,9 +794,8 @@
       throws EvalException {
 
     List<String> cxxBuiltInIncludeDirectories =
-        cxxBuiltInIncludeDirectoriesUnchecked.getContents(
-            String.class, "cxx_builtin_include_directories");
-
+        Sequence.cast(
+            cxxBuiltInIncludeDirectoriesUnchecked, String.class, "cxx_builtin_include_directories");
 
     ImmutableList.Builder<Feature> featureBuilder = ImmutableList.builder();
     for (Object feature : features) {
@@ -1412,25 +1413,28 @@
   /** Returns a list of strings from a field of a {@link SkylarkInfo}. */
   private static ImmutableList<String> getStringListFromSkylarkProviderField(
       SkylarkInfo provider, String fieldName) throws EvalException {
-    return ImmutableList.copyOf(
-        Sequence.castSkylarkListOrNoneToList(
-            getValueOrNull(provider, fieldName), String.class, fieldName));
+    Object v = getValueOrNull(provider, fieldName);
+    return v == null
+        ? ImmutableList.of()
+        : ImmutableList.copyOf(Sequence.noneableCast(v, String.class, fieldName));
   }
 
   /** Returns a set of strings from a field of a {@link SkylarkInfo}. */
   private static ImmutableSet<String> getStringSetFromSkylarkProviderField(
       SkylarkInfo provider, String fieldName) throws EvalException {
-    return ImmutableSet.copyOf(
-        Sequence.castSkylarkListOrNoneToList(
-            getValueOrNull(provider, fieldName), String.class, fieldName));
+    Object v = getValueOrNull(provider, fieldName);
+    return v == null
+        ? ImmutableSet.of()
+        : ImmutableSet.copyOf(Sequence.noneableCast(v, String.class, fieldName));
   }
 
   /** Returns a list of SkylarkInfo providers from a field of a {@link SkylarkInfo}. */
   private static ImmutableList<SkylarkInfo> getSkylarkProviderListFromSkylarkField(
       SkylarkInfo provider, String fieldName) throws EvalException {
-    return ImmutableList.copyOf(
-        Sequence.castSkylarkListOrNoneToList(
-            getValueOrNull(provider, fieldName), SkylarkInfo.class, fieldName));
+    Object v = getValueOrNull(provider, fieldName);
+    return v == null
+        ? ImmutableList.of()
+        : ImmutableList.copyOf(Sequence.noneableCast(v, SkylarkInfo.class, fieldName));
   }
 
   private static void getLegacyArtifactNamePatterns(
@@ -1522,7 +1526,7 @@
                     actions.getRuleContext().isAllowTagsPropagation()))
             .setGrepIncludes(convertFromNoneable(grepIncludes, /* defaultValue= */ null))
             .addNonCodeLinkerInputs(
-                additionalInputs.getContents(Artifact.class, "additional_inputs"))
+                Sequence.cast(additionalInputs, Artifact.class, "additional_inputs"))
             .setShouldCreateStaticLibraries(!disallowStaticLibraries)
             .setShouldCreateDynamicLibrary(
                 !disallowDynamicLibraries
@@ -1531,7 +1535,7 @@
                         .isEnabled(CppRuleClasses.TARGETS_WINDOWS))
             .setStaticLinkType(staticLinkTargetType)
             .setDynamicLinkType(LinkTargetType.NODEPS_DYNAMIC_LIBRARY)
-            .addLinkopts(userLinkFlags.getContents(String.class, "user_link_flags"));
+            .addLinkopts(Sequence.cast(userLinkFlags, String.class, "user_link_flags"));
     try {
       CcLinkingOutputs ccLinkingOutputs = CcLinkingOutputs.EMPTY;
       ImmutableList<LibraryToLink> libraryToLink = ImmutableList.of();
@@ -1552,7 +1556,8 @@
           CcLinkingContext.merge(
               ImmutableList.<CcLinkingContext>builder()
                   .add(linkingContext)
-                  .addAll(linkingContexts.getContents(CcLinkingContext.class, "linking_contexts"))
+                  .addAll(
+                      Sequence.cast(linkingContexts, CcLinkingContext.class, "linking_contexts"))
                   .build()),
           ccLinkingOutputs);
     } catch (RuleErrorException e) {
@@ -1623,9 +1628,9 @@
       Sequence<?> additionalInputs,
       StarlarkThread thread)
       throws EvalException, InterruptedException {
-    List<Artifact> sources = sourcesUnchecked.getContents(Artifact.class, "srcs");
-    List<Artifact> publicHeaders = publicHeadersUnchecked.getContents(Artifact.class, "srcs");
-    List<Artifact> privateHeaders = privateHeadersUnchecked.getContents(Artifact.class, "srcs");
+    List<Artifact> sources = Sequence.cast(sourcesUnchecked, Artifact.class, "srcs");
+    List<Artifact> publicHeaders = Sequence.cast(publicHeadersUnchecked, Artifact.class, "srcs");
+    List<Artifact> privateHeaders = Sequence.cast(privateHeadersUnchecked, Artifact.class, "srcs");
 
     SkylarkActionFactory actions = skylarkActionFactoryApi;
     CcToolchainProvider ccToolchainProvider = convertFromNoneable(skylarkCcToolchainProvider, null);
@@ -1671,32 +1676,32 @@
             .addPrivateHeaders(privateHeaders)
             .addSources(sources)
             .addCcCompilationContexts(
-                ccCompilationContexts.getContents(
-                    CcCompilationContext.class, "compilation_contexts"))
+                Sequence.cast(
+                    ccCompilationContexts, CcCompilationContext.class, "compilation_contexts"))
             .addIncludeDirs(
-                includes.getContents(String.class, "includes").stream()
+                Sequence.cast(includes, String.class, "includes").stream()
                     .map(PathFragment::create)
                     .collect(ImmutableList.toImmutableList()))
             .addQuoteIncludeDirs(
-                quoteIncludes.getContents(String.class, "quote_includes").stream()
+                Sequence.cast(quoteIncludes, String.class, "quote_includes").stream()
                     .map(PathFragment::create)
                     .collect(ImmutableList.toImmutableList()))
             .addSystemIncludeDirs(
-                systemIncludes.getContents(String.class, "system_includes").stream()
+                Sequence.cast(systemIncludes, String.class, "system_includes").stream()
                     .map(PathFragment::create)
                     .collect(ImmutableList.toImmutableList()))
             .addFrameworkIncludeDirs(
-                frameworkIncludes.getContents(String.class, "framework_includes").stream()
+                Sequence.cast(frameworkIncludes, String.class, "framework_includes").stream()
                     .map(PathFragment::create)
                     .collect(ImmutableList.toImmutableList()))
-            .addDefines(defines.getContents(String.class, "defines"))
-            .addNonTransitiveDefines(localDefines.getContents(String.class, "local_defines"))
+            .addDefines(Sequence.cast(defines, String.class, "defines"))
+            .addNonTransitiveDefines(Sequence.cast(localDefines, String.class, "local_defines"))
             .setCopts(
                 ImmutableList.copyOf(
-                    userCompileFlags.getContents(String.class, "user_compile_flags")))
+                    Sequence.cast(userCompileFlags, String.class, "user_compile_flags")))
             .addAdditionalCompilationInputs(headersForClifDoNotUseThisParam)
             .addAdditionalCompilationInputs(
-                additionalInputs.getContents(Artifact.class, "additional_inputs"))
+                Sequence.cast(additionalInputs, Artifact.class, "additional_inputs"))
             .addAditionalIncludeScanningRoots(headersForClifDoNotUseThisParam)
             .setPurpose(common.getPurpose(getSemantics()));
     if (disallowNopicOutputs) {
@@ -1783,12 +1788,12 @@
             .setLinkingMode(linkDepsStatically ? LinkingMode.STATIC : LinkingMode.DYNAMIC)
             .setIsStampingEnabled(isStampingEnabled)
             .addNonCodeLinkerInputs(
-                additionalInputs.getContents(Artifact.class, "additional_inputs"))
+                Sequence.cast(additionalInputs, Artifact.class, "additional_inputs"))
             .setDynamicLinkType(dynamicLinkTargetType)
             .addCcLinkingContexts(
-                linkingContexts.getContents(CcLinkingContext.class, "linking_contexts"))
+                Sequence.cast(linkingContexts, CcLinkingContext.class, "linking_contexts"))
             .setShouldCreateStaticLibraries(false)
-            .addLinkopts(userLinkFlags.getContents(String.class, "user_link_flags"))
+            .addLinkopts(Sequence.cast(userLinkFlags, String.class, "user_link_flags"))
             .emitInterfaceSharedLibraries(
                 dynamicLinkTargetType == LinkTargetType.DYNAMIC_LIBRARY
                     && actualFeatureConfiguration.isEnabled(CppRuleClasses.TARGETS_WINDOWS)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/BootClassPathInfo.java b/src/main/java/com/google/devtools/build/lib/rules/java/BootClassPathInfo.java
index 7b78c72..c2e6cf9 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/BootClassPathInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/BootClassPathInfo.java
@@ -30,7 +30,6 @@
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.Location;
 import com.google.devtools.build.lib.syntax.Sequence;
-import com.google.devtools.build.lib.syntax.SkylarkType;
 import com.google.devtools.build.lib.syntax.Starlark;
 import com.google.devtools.build.lib.syntax.StarlarkThread;
 import com.google.devtools.build.lib.syntax.StarlarkValue;
@@ -84,16 +83,17 @@
     private static NestedSet<Artifact> getBootClassPath(Sequence<?> bootClassPathList)
         throws EvalException {
       return NestedSetBuilder.wrap(
-          Order.STABLE_ORDER,
-          Sequence.castList(bootClassPathList, Artifact.class, "bootclasspath"));
+          Order.STABLE_ORDER, Sequence.cast(bootClassPathList, Artifact.class, "bootclasspath"));
     }
 
     private static Artifact getSystem(Object systemOrNone) throws EvalException {
       if (systemOrNone == Starlark.NONE) {
         return null;
       }
-      SkylarkType.checkType(systemOrNone, Artifact.class, "system");
-      return (Artifact) systemOrNone;
+      if (systemOrNone instanceof Artifact) {
+        return (Artifact) systemOrNone;
+      }
+      throw Starlark.errorf("for system, got %s, want File or None", Starlark.type(systemOrNone));
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaSkylarkCommon.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaSkylarkCommon.java
index 64bfdd9..e12a4fa 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaSkylarkCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaSkylarkCommon.java
@@ -79,26 +79,32 @@
     return JavaInfoBuildHelper.getInstance()
         .createJavaCompileAction(
             skylarkRuleContext,
-            sourceJars.getContents(Artifact.class, "source_jars"),
-            sourceFiles.getContents(Artifact.class, "source_files"),
+            Sequence.cast(sourceJars, Artifact.class, "source_jars"),
+            Sequence.cast(sourceFiles, Artifact.class, "source_files"),
             outputJar,
             outputSourceJar == Starlark.NONE ? null : (Artifact) outputSourceJar,
-            javacOpts.getContents(String.class, "javac_opts"),
-            deps.getContents(JavaInfo.class, "deps"),
-            experimentalLocalCompileTimeDeps.getContents(
-                JavaInfo.class, "experimental_local_compile_time_deps"),
-            exports.getContents(JavaInfo.class, "exports"),
-            plugins.getContents(JavaInfo.class, "plugins"),
-            exportedPlugins.getContents(JavaInfo.class, "exported_plugins"),
-            annotationProcessorAdditionalInputs.getContents(
-                Artifact.class, "annotation_processor_additional_inputs"),
-            annotationProcessorAdditionalOutputs.getContents(
-                Artifact.class, "annotation_processor_additional_outputs"),
+            Sequence.cast(javacOpts, String.class, "javac_opts"),
+            Sequence.cast(deps, JavaInfo.class, "deps"),
+            Sequence.cast(
+                experimentalLocalCompileTimeDeps,
+                JavaInfo.class,
+                "experimental_local_compile_time_deps"),
+            Sequence.cast(exports, JavaInfo.class, "exports"),
+            Sequence.cast(plugins, JavaInfo.class, "plugins"),
+            Sequence.cast(exportedPlugins, JavaInfo.class, "exported_plugins"),
+            Sequence.cast(
+                annotationProcessorAdditionalInputs,
+                Artifact.class,
+                "annotation_processor_additional_inputs"),
+            Sequence.cast(
+                annotationProcessorAdditionalOutputs,
+                Artifact.class,
+                "annotation_processor_additional_outputs"),
             strictDepsMode,
             javaToolchain,
             hostJavabase,
-            ImmutableList.copyOf(sourcepathEntries.getContents(Artifact.class, "sourcepath")),
-            resources.getContents(Artifact.class, "resources"),
+            ImmutableList.copyOf(Sequence.cast(sourcepathEntries, Artifact.class, "sourcepath")),
+            Sequence.cast(resources, Artifact.class, "resources"),
             neverlink,
             javaSemantics,
             thread);
@@ -140,8 +146,8 @@
             actions,
             outputJar,
             /* outputSourceJar= */ null,
-            sourceFiles.getContents(Artifact.class, "sources"),
-            sourceJars.getContents(Artifact.class, "source_jars"),
+            Sequence.cast(sourceFiles, Artifact.class, "sources"),
+            Sequence.cast(sourceJars, Artifact.class, "source_jars"),
             javaToolchain,
             hostJavabase);
   }
@@ -158,7 +164,7 @@
   @Override
   public JavaInfo mergeJavaProviders(Sequence<?> providers /* <JavaInfo> expected. */)
       throws EvalException {
-    return JavaInfo.merge(providers.getContents(JavaInfo.class, "providers"));
+    return JavaInfo.merge(Sequence.cast(providers, JavaInfo.class, "providers"));
   }
 
   // TODO(b/65113771): Remove this method because it's incorrect.
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/MessageBundleInfo.java b/src/main/java/com/google/devtools/build/lib/rules/java/MessageBundleInfo.java
index 5b6c461..8bfe28e 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/MessageBundleInfo.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/MessageBundleInfo.java
@@ -63,7 +63,7 @@
         useStarlarkThread = true)
     public MessageBundleInfo messageBundleInfo(Sequence<?> messages, StarlarkThread thread)
         throws EvalException {
-      List<Artifact> messagesList = Sequence.castList(messages, Artifact.class, "messages");
+      List<Artifact> messagesList = Sequence.cast(messages, Artifact.class, "messages");
       return new MessageBundleInfo(ImmutableList.copyOf(messagesList), thread.getCallerLocation());
     }
   }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/AppleSkylarkCommon.java b/src/main/java/com/google/devtools/build/lib/rules/objc/AppleSkylarkCommon.java
index 8f6827c..4c18e8a 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/AppleSkylarkCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/AppleSkylarkCommon.java
@@ -243,8 +243,8 @@
       AppleBinaryOutput appleBinaryOutput =
           AppleBinary.linkMultiArchBinary(
               ruleContext,
-              ImmutableList.copyOf(extraLinkopts.getContents(String.class, "extra_linkopts")),
-              Sequence.castList(extraLinkInputs, Artifact.class, "extra_link_inputs"));
+              ImmutableList.copyOf(Sequence.cast(extraLinkopts, String.class, "extra_linkopts")),
+              Sequence.cast(extraLinkInputs, Artifact.class, "extra_link_inputs"));
       return createAppleBinaryOutputSkylarkStruct(appleBinaryOutput, thread);
     } catch (RuleErrorException | ActionConflictException exception) {
       throw new EvalException(null, exception);
diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PyProviderUtils.java b/src/main/java/com/google/devtools/build/lib/rules/python/PyProviderUtils.java
index d363eeb..50995e0 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/python/PyProviderUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/python/PyProviderUtils.java
@@ -24,7 +24,7 @@
 import com.google.devtools.build.lib.packages.StructImpl;
 import com.google.devtools.build.lib.rules.cpp.CppFileTypes;
 import com.google.devtools.build.lib.syntax.EvalException;
-import com.google.devtools.build.lib.syntax.SkylarkType;
+import com.google.devtools.build.lib.syntax.Starlark;
 import com.google.devtools.build.lib.util.FileType;
 
 /**
@@ -63,15 +63,15 @@
    */
   public static StructImpl getLegacyProvider(TransitiveInfoCollection target) throws EvalException {
     Object info = target.get(PyStructUtils.PROVIDER_NAME);
-    if (info == null) {
-      throw new EvalException(/*location=*/ null, "Target does not have 'py' provider");
+    if (info instanceof StructImpl) {
+      return (StructImpl) info;
     }
-    return SkylarkType.cast(
-        info,
-        StructImpl.class,
-        null,
-        "'%s' provider should be a struct",
-        PyStructUtils.PROVIDER_NAME);
+    if (info == null) {
+      throw Starlark.errorf("Target does not have '%s' provider", PyStructUtils.PROVIDER_NAME);
+    }
+    throw Starlark.errorf(
+        "'%s' provider should be a struct (got %s)",
+        PyStructUtils.PROVIDER_NAME, Starlark.type(info));
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PyStructUtils.java b/src/main/java/com/google/devtools/build/lib/rules/python/PyStructUtils.java
index 245a274..bf67b96 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/python/PyStructUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/python/PyStructUtils.java
@@ -26,6 +26,7 @@
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.EvalUtils;
 import com.google.devtools.build.lib.syntax.SkylarkType;
+import com.google.devtools.build.lib.syntax.Starlark;
 
 /** Static helper class for creating and accessing instances of the legacy "py" struct provider. */
 // TODO(b/153363654): Remove this file.
@@ -101,34 +102,21 @@
    */
   public static NestedSet<Artifact> getTransitiveSources(StructImpl info) throws EvalException {
     Object fieldValue = getValue(info, TRANSITIVE_SOURCES);
-    Depset castValue =
-        SkylarkType.cast(
-            fieldValue,
-            Depset.class,
-            Artifact.class,
-            null,
-            "'%s' provider's '%s' field should be a depset of Files (got a '%s')",
-            PROVIDER_NAME,
-            TRANSITIVE_SOURCES,
-            EvalUtils.getDataTypeName(fieldValue, /*fullDetails=*/ true));
+    // TODO(adonovan): factor with Depset.getSetFromParam and simplify.
     try {
-      NestedSet<Artifact> unwrappedValue = castValue.getSet(Artifact.class);
-      if (!unwrappedValue.getOrder().isCompatible(Order.COMPILE_ORDER)) {
-        throw new EvalException(
-            /*location=*/ null,
-            String.format(
-                "Incompatible depset order for 'transitive_sources': expected 'default' or "
-                    + "'postorder', but got '%s'",
-                unwrappedValue.getOrder().getSkylarkName()));
+      Depset depset = (Depset) fieldValue; // (ClassCastException)
+      NestedSet<Artifact> set = depset.getSet(Artifact.class); // (TypeException)
+      if (!set.getOrder().isCompatible(Order.COMPILE_ORDER)) {
+        throw Starlark.errorf(
+            "Incompatible depset order for 'transitive_sources': expected 'default' or "
+                + "'postorder', but got '%s'",
+            set.getOrder().getSkylarkName());
       }
-      return unwrappedValue;
-    } catch (Depset.TypeException exception) {
-      throw new EvalException(
-          null,
-          String.format(
-              "expected field '%s' to be a depset of type 'file', but was a depset of type '%s'",
-              TRANSITIVE_SOURCES, castValue.getContentType()),
-          exception);
+      return set;
+    } catch (@SuppressWarnings("UnusedException") ClassCastException | Depset.TypeException ex) {
+      throw Starlark.errorf(
+          "'%s' provider's '%s' field was %s, want depset of Files",
+          PROVIDER_NAME, TRANSITIVE_SOURCES, EvalUtils.getDataTypeName(fieldValue, true));
     }
   }
 
@@ -138,15 +126,13 @@
    * @throws EvalException if the field exists and is not a boolean
    */
   public static boolean getUsesSharedLibraries(StructImpl info) throws EvalException {
-    Object fieldValue = getValue(info, USES_SHARED_LIBRARIES);
-    return SkylarkType.cast(
-        fieldValue,
-        Boolean.class,
-        null,
-        "'%s' provider's '%s' field should be a boolean (got a '%s')",
-        PROVIDER_NAME,
-        USES_SHARED_LIBRARIES,
-        EvalUtils.getDataTypeName(fieldValue, /*fullDetails=*/ true));
+    Object v = getValue(info, USES_SHARED_LIBRARIES);
+    if (v instanceof Boolean) {
+      return (Boolean) v;
+    }
+    throw Starlark.errorf(
+        "'%s' provider's '%s' field was %s, want bool",
+        PROVIDER_NAME, USES_SHARED_LIBRARIES, Starlark.type(v));
   }
 
   /**
@@ -156,25 +142,14 @@
    */
   public static NestedSet<String> getImports(StructImpl info) throws EvalException {
     Object fieldValue = getValue(info, IMPORTS);
-    Depset castValue =
-        SkylarkType.cast(
-            fieldValue,
-            Depset.class,
-            String.class,
-            null,
-            "'%s' provider's '%s' field should be a depset of strings (got a '%s')",
-            PROVIDER_NAME,
-            IMPORTS,
-            EvalUtils.getDataTypeNameFromClass(fieldValue.getClass()));
+    // TODO(adonovan): factor with Depset.getSetFromParam and simplify.
     try {
-      return castValue.getSet(String.class);
-    } catch (Depset.TypeException exception) {
-      throw new EvalException(
-          null,
-          String.format(
-              "expected field '%s' to be a depset of type 'file', but was a depset of type '%s'",
-              IMPORTS, castValue.getContentType()),
-          exception);
+      Depset depset = (Depset) fieldValue; // (ClassCastException)
+      return depset.getSet(String.class); // (TypeException)
+    } catch (@SuppressWarnings("UnusedException") ClassCastException | Depset.TypeException ex) {
+      throw Starlark.errorf(
+          "'%s' provider's '%s' field was %s, want depset of strings",
+          PROVIDER_NAME, IMPORTS, EvalUtils.getDataTypeName(fieldValue, true));
     }
   }
 
@@ -184,15 +159,13 @@
    * @throws EvalException if the field exists and is not a boolean
    */
   public static boolean getHasPy2OnlySources(StructImpl info) throws EvalException {
-    Object fieldValue = getValue(info, HAS_PY2_ONLY_SOURCES);
-    return SkylarkType.cast(
-        fieldValue,
-        Boolean.class,
-        null,
-        "'%s' provider's '%s' field should be a boolean (got a '%s')",
-        PROVIDER_NAME,
-        HAS_PY2_ONLY_SOURCES,
-        EvalUtils.getDataTypeNameFromClass(fieldValue.getClass()));
+    Object v = getValue(info, HAS_PY2_ONLY_SOURCES);
+    if (v instanceof Boolean) {
+      return (Boolean) v;
+    }
+    throw Starlark.errorf(
+        "'%s' provider's '%s' field was %s, want bool",
+        PROVIDER_NAME, HAS_PY2_ONLY_SOURCES, Starlark.type(v));
   }
 
   /**
@@ -201,15 +174,13 @@
    * @throws EvalException if the field exists and is not a boolean
    */
   public static boolean getHasPy3OnlySources(StructImpl info) throws EvalException {
-    Object fieldValue = getValue(info, HAS_PY3_ONLY_SOURCES);
-    return SkylarkType.cast(
-        fieldValue,
-        Boolean.class,
-        null,
-        "'%s' provider's '%s' field should be a boolean (got a '%s')",
-        PROVIDER_NAME,
-        HAS_PY3_ONLY_SOURCES,
-        EvalUtils.getDataTypeNameFromClass(fieldValue.getClass()));
+    Object v = getValue(info, HAS_PY3_ONLY_SOURCES);
+    if (v instanceof Boolean) {
+      return (Boolean) v;
+    }
+    throw Starlark.errorf(
+        "'%s' provider's '%s' field was %s, want bool",
+        PROVIDER_NAME, HAS_PY3_ONLY_SOURCES, Starlark.type(v));
   }
 
   public static Builder builder() {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/test/SkylarkTestingModule.java b/src/main/java/com/google/devtools/build/lib/rules/test/SkylarkTestingModule.java
index bc8bc28..f2b9703 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/test/SkylarkTestingModule.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/test/SkylarkTestingModule.java
@@ -25,13 +25,13 @@
   @Override
   public ExecutionInfo executionInfo(Dict<?, ?> requirements /* <String, String> */)
       throws EvalException {
-    return new ExecutionInfo(requirements.getContents(String.class, String.class, "requirements"));
+    return new ExecutionInfo(Dict.cast(requirements, String.class, String.class, "requirements"));
   }
 
   @Override
   public TestEnvironmentInfo testEnvironment(Dict<?, ?> environment /* <String, String> */)
       throws EvalException {
     return new TestEnvironmentInfo(
-        environment.getContents(String.class, String.class, "environment"));
+        Dict.cast(environment, String.class, String.class, "environment"));
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
index 56cf240..79aec7b 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkylarkAspectFactory.java
@@ -33,11 +33,11 @@
 import com.google.devtools.build.lib.packages.StructImpl;
 import com.google.devtools.build.lib.packages.StructProvider;
 import com.google.devtools.build.lib.packages.Target;
+import com.google.devtools.build.lib.syntax.Dict;
 import com.google.devtools.build.lib.syntax.EvalException;
 import com.google.devtools.build.lib.syntax.EvalExceptionWithStackTrace;
 import com.google.devtools.build.lib.syntax.EvalUtils;
 import com.google.devtools.build.lib.syntax.Mutability;
-import com.google.devtools.build.lib.syntax.SkylarkType;
 import com.google.devtools.build.lib.syntax.Starlark;
 import com.google.devtools.build.lib.syntax.StarlarkThread;
 import com.google.devtools.build.lib.syntax.StarlarkValue;
@@ -142,16 +142,15 @@
           if (field.equals("output_groups")) {
             addOutputGroups(struct.getValue(field), builder);
           } else if (field.equals("providers")) {
-            Object value = struct.getValue(field);
-            Iterable<?> providers =
-                SkylarkType.cast(
-                    value,
-                    Iterable.class,
-                    null,
-                    "The value for \"providers\" should be a list of declared providers, "
-                        + "got %s instead",
-                    EvalUtils.getDataTypeName(value, false));
-            addDeclaredProviders(builder, providers);
+            Object providers = struct.getValue(field);
+            // TODO(adonovan): can we be more specific than iterable, and use Sequence.cast?
+            if (!(providers instanceof Iterable)) {
+              throw Starlark.errorf(
+                  "The value for \"providers\" should be a list of declared providers, "
+                      + "got %s instead",
+                  Starlark.type(providers));
+            }
+            addDeclaredProviders(builder, (Iterable<?>) providers);
           } else {
             builder.addSkylarkTransitiveInfo(field, struct.getValue(field));
           }
@@ -170,31 +169,25 @@
       ConfiguredAspect.Builder builder, Iterable<?> aspectSkylarkObject) throws EvalException {
     int i = 0;
     for (Object o : aspectSkylarkObject) {
-      Info declaredProvider =
-          SkylarkType.cast(
-              o,
-              Info.class,
-              null,
-              "A return value of an aspect implementation function should be "
-                  + "a sequence of declared providers, instead got a %s at index %d",
-              o.getClass(),
-              i);
-      builder.addSkylarkDeclaredProvider(declaredProvider);
+      if (!(o instanceof Info)) {
+        throw Starlark.errorf(
+            "A return value of an aspect implementation function should be "
+                + "a sequence of declared providers, instead got a %s at index %d",
+            Starlark.type(o), i);
+      }
+      builder.addSkylarkDeclaredProvider((Info) o);
       i++;
     }
   }
 
-  private static void addOutputGroups(Object value, ConfiguredAspect.Builder builder)
+  private static void addOutputGroups(Object outputGroups, ConfiguredAspect.Builder builder)
       throws EvalException {
-    Map<String, StarlarkValue> outputGroups =
-        SkylarkType.castMap(value, String.class, StarlarkValue.class, "output_groups");
-
-    for (String outputGroup : outputGroups.keySet()) {
-      StarlarkValue objects = outputGroups.get(outputGroup);
-
+    for (Map.Entry<String, StarlarkValue> entry :
+        Dict.cast(outputGroups, String.class, StarlarkValue.class, "output_groups").entrySet()) {
       builder.addOutputGroup(
-          outputGroup,
-          SkylarkRuleConfiguredTargetUtil.convertToOutputGroupValue(outputGroup, objects));
+          entry.getKey(),
+          SkylarkRuleConfiguredTargetUtil.convertToOutputGroupValue(
+              entry.getKey(), entry.getValue()));
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Dict.java b/src/main/java/com/google/devtools/build/lib/syntax/Dict.java
index 85c86bb..3c70d4e 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Dict.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Dict.java
@@ -14,6 +14,7 @@
 
 package com.google.devtools.build.lib.syntax;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
 import com.google.devtools.build.lib.skylarkinterface.Param;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable;
@@ -477,65 +478,42 @@
   }
 
   /**
-   * If {@code obj} is a {@code Dict}, casts it to an unmodifiable {@code Map<K, V>} after checking
-   * that each of its entries has key type {@code keyType} and value type {@code valueType}. If
-   * {@code obj} is {@code None} or null, treats it as an empty dict.
-   *
-   * <p>The returned map may or may not be a view that is affected by updates to the original dict.
-   *
-   * @param obj the object to cast. null and None are treated as an empty dict.
-   * @param keyType the expected type of all the dict's keys
-   * @param valueType the expected type of all the dict's values
-   * @param description a description of the argument being converted, or null, for debugging
+   * Casts a non-null Starlark value {@code x} to a {@code Dict<K, V>} after checking that all keys
+   * and values are instances of {@code keyType} and {@code valueType}, respectively. On error, it
+   * throws an EvalException whose message includes {@code what}, ideally a string literal, as a
+   * description of the role of {@code x}. If x is null, it returns an immutable empty dict.
    */
-  public static <K, V> Map<K, V> castSkylarkDictOrNoneToDict(
-      Object obj, Class<K> keyType, Class<V> valueType, @Nullable String description)
+  public static <K, V> Dict<K, V> cast(Object x, Class<K> keyType, Class<V> valueType, String what)
       throws EvalException {
-    if (EvalUtils.isNullOrNone(obj)) {
-      return empty();
+    Preconditions.checkNotNull(x);
+    if (!(x instanceof Dict)) {
+      throw Starlark.errorf("got %s for '%s', want dict", Starlark.type(x), what);
     }
-    if (obj instanceof Dict) {
-      return ((Dict<?, ?>) obj).getContents(keyType, valueType, description);
-    }
-    throw Starlark.errorf(
-        "%s is not of expected type dict or NoneType",
-        description == null ? Starlark.repr(obj) : String.format("'%s'", description));
-  }
 
-  /**
-   * Returns an unmodifiable view of this Dict coerced to type {@code Dict<X, Y>}, after
-   * superficially checking that all keys and values are of class {@code keyType} and {@code
-   * valueType} respectively.
-   *
-   * <p>The returned map is a view that reflects subsequent updates to the original dict. If such
-   * updates should insert keys or values of types other than X or Y respectively, the reference
-   * returned by getContents will have a false type that may cause the program to fail in unexpected
-   * and hard-to-debug ways.
-   *
-   * <p>The dynamic checks are necessarily superficial if either of X or Y is itself a parameterized
-   * type. For example, if Y is {@code List<String>}, getContents checks that the dict values are
-   * instances of List, but it does not and cannot check that all the elements of those lists are
-   * Strings. If one of the dict values in fact a List of Integer, the returned reference will again
-   * have a false type.
-   *
-   * @param keyType the expected class of keys
-   * @param valueType the expected class of values
-   * @param description a description of the argument being converted, or null, for debugging
-   */
-  @SuppressWarnings("unchecked")
-  public <X, Y> Map<X, Y> getContents(
-      Class<X> keyType, Class<Y> valueType, @Nullable String description) throws EvalException {
-    Object keyDescription =
-        description == null ? null : Printer.formattable("'%s' key", description);
-    Object valueDescription =
-        description == null ? null : Printer.formattable("'%s' value", description);
-    for (Map.Entry<?, ?> e : this.entrySet()) {
-      SkylarkType.checkType(e.getKey(), keyType, keyDescription);
-      if (e.getValue() != null) {
-        SkylarkType.checkType(e.getValue(), valueType, valueDescription);
+    for (Map.Entry<?, ?> e : ((Map<?, ?>) x).entrySet()) {
+      if (!keyType.isAssignableFrom(e.getKey().getClass())
+          || !valueType.isAssignableFrom(e.getValue().getClass())) {
+        // TODO(adonovan): change message to "found <K2, V2> entry",
+        // without suggesting that the entire dict is <K2, V2>.
+        throw Starlark.errorf(
+            "got dict<%s, %s> for '%s', want dict<%s, %s>",
+            Starlark.type(e.getKey()),
+            Starlark.type(e.getValue()),
+            what,
+            EvalUtils.getDataTypeNameFromClass(keyType),
+            EvalUtils.getDataTypeNameFromClass(valueType));
       }
     }
-    return Collections.unmodifiableMap((Dict<X, Y>) this);
+
+    @SuppressWarnings("unchecked") // safe
+    Dict<K, V> res = (Dict<K, V>) x;
+    return res;
+  }
+
+  /** Like {@link #cast}, but if x is None, returns an empty Dict. */
+  public static <K, V> Dict<K, V> noneableCast(
+      Object x, Class<K> keyType, Class<V> valueType, String what) throws EvalException {
+    return x == Starlark.NONE ? empty() : cast(x, keyType, valueType, what);
   }
 
   @Override
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 2dc79e7..6ff8840 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
@@ -15,7 +15,6 @@
 package com.google.devtools.build.lib.syntax;
 
 import com.google.common.base.Ascii;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
@@ -928,8 +927,8 @@
       result =
           Depset.fromDirectAndTransitive(
               order,
-              listFromNoneable(direct, Object.class, "direct"),
-              listFromNoneable(transitive, Depset.class, "transitive"),
+              Sequence.noneableCast(direct, Object.class, "direct"),
+              Sequence.noneableCast(transitive, Depset.class, "transitive"),
               semantics.incompatibleAlwaysCheckDepsetElements());
     } else {
       if (x != Starlark.NONE) {
@@ -956,16 +955,6 @@
     return result;
   }
 
-  private static <T> List<T> listFromNoneable(
-      Object listOrNone, Class<T> objectType, String paramName) throws EvalException {
-    if (listOrNone != Starlark.NONE) {
-      SkylarkType.checkType(listOrNone, Sequence.class, paramName);
-      return ((Sequence<?>) listOrNone).getContents(objectType, paramName);
-    } else {
-      return ImmutableList.of();
-    }
-  }
-
   private static Depset legacyDepsetConstructor(
       Object items, Order order, Object direct, Object transitive, StarlarkSemantics semantics)
       throws EvalException {
@@ -981,22 +970,13 @@
     }
 
     // Non-legacy behavior: either 'transitive' or 'direct' were specified.
-    List<Object> directElements;
-    if (direct != Starlark.NONE) {
-      SkylarkType.checkType(direct, Sequence.class, "direct");
-      directElements = ((Sequence<?>) direct).getContents(Object.class, "direct");
-    } else {
-      SkylarkType.checkType(items, Sequence.class, "items");
-      directElements = ((Sequence<?>) items).getContents(Object.class, "items");
-    }
+    List<Object> directElements =
+        direct != Starlark.NONE
+            ? Sequence.cast(direct, Object.class, "direct")
+            : Sequence.cast(items, Object.class, "items");
 
-    List<Depset> transitiveList;
-    if (transitive != Starlark.NONE) {
-      SkylarkType.checkType(transitive, Sequence.class, "transitive");
-      transitiveList = ((Sequence<?>) transitive).getContents(Depset.class, "transitive");
-    } else {
-      transitiveList = ImmutableList.of();
-    }
+    List<Depset> transitiveList = Sequence.noneableCast(transitive, Depset.class, "transitive");
+
     return Depset.fromDirectAndTransitive(
         order, directElements, transitiveList, semantics.incompatibleAlwaysCheckDepsetElements());
   }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Printer.java b/src/main/java/com/google/devtools/build/lib/syntax/Printer.java
index ccaf69a..838a588 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Printer.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Printer.java
@@ -16,8 +16,6 @@
 import com.google.common.base.Strings;
 import java.io.IOException;
 import java.util.Arrays;
-import java.util.Formattable;
-import java.util.Formatter;
 import java.util.IllegalFormatException;
 import java.util.List;
 import java.util.Map;
@@ -132,28 +130,6 @@
   private Printer() {}
 
   /**
-   * Perform Python-style string formatting, lazily.
-   *
-   * @param pattern a format string.
-   * @param arguments positional arguments.
-   * @return the formatted string.
-   */
-  static Formattable formattable(final String pattern, Object... arguments) {
-    final List<Object> args = Arrays.asList(arguments);
-    return new Formattable() {
-      @Override
-      public String toString() {
-        return Starlark.formatWithList(pattern, args);
-      }
-
-      @Override
-      public void formatTo(Formatter formatter, int flags, int width, int precision) {
-        Printer.getPrinter(formatter.out()).formatWithList(pattern, args);
-      }
-    };
-  }
-
-  /**
    * Append a char to a buffer. In case of {@link IOException} throw an {@link AssertionError}
    * instead
    *
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Sequence.java b/src/main/java/com/google/devtools/build/lib/syntax/Sequence.java
index d743ca0..726a3ba 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Sequence.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Sequence.java
@@ -14,13 +14,12 @@
 
 package com.google.devtools.build.lib.syntax;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModule;
 import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory;
-import java.util.Collections;
 import java.util.List;
 import java.util.RandomAccess;
-import javax.annotation.Nullable;
 
 /**
  * A Sequence is a finite iterable sequence of Starlark values, such as a list or tuple.
@@ -73,64 +72,32 @@
   Sequence<E> getSlice(Mutability mu, int start, int stop, int step);
 
   /**
-   * Casts a {@code List<?>} to an unmodifiable {@code List<T>}, after checking that its contents
-   * all have type {@code type}.
-   *
-   * <p>The returned list may or may not be a view that is affected by updates to the original list.
-   *
-   * @param list the original list to cast
-   * @param type the expected type of all the list's elements
-   * @param description a description of the argument being converted, or null, for debugging
+   * Casts a non-null Starlark value {@code x} to a {@code Sequence<T>}, after checking that each
+   * element is an instance of {@code elemType}. On error, it throws an EvalException whose message
+   * includes {@code what}, ideally a string literal, as a description of the role of {@code x}.
    */
-  // We could have used bounded generics to ensure that only downcasts are possible (i.e. cast
-  // List<S> to List<T extends S>), but this would be less convenient for some callers, and it would
-  // disallow casting an empty list to any type.
-  // TODO(adonovan): this method doesn't belong here.
-  @SuppressWarnings("unchecked")
-  public static <T> List<T> castList(List<?> list, Class<T> type, @Nullable String description)
+  public static <T> Sequence<T> cast(Object x, Class<T> elemType, String what)
       throws EvalException {
-    Object desc = description == null ? null : Printer.formattable("'%s' element", description);
-    for (Object value : list) {
-      SkylarkType.checkType(value, type, desc);
+    Preconditions.checkNotNull(x);
+    if (!(x instanceof Sequence)) {
+      throw Starlark.errorf("for %s, got %s, want sequence", what, Starlark.type(x));
     }
-    return Collections.unmodifiableList((List<T>) list);
+    int i = 0;
+    for (Object elem : (Sequence) x) {
+      if (!elemType.isAssignableFrom(elem.getClass())) {
+        throw Starlark.errorf(
+            "at index %d of %s, got element of type %s, want %s",
+            i, what, Starlark.type(elem), EvalUtils.getDataTypeNameFromClass(elemType));
+      }
+    }
+    @SuppressWarnings("unchecked") // safe
+    Sequence<T> result = (Sequence) x;
+    return result;
   }
 
-  /**
-   * If {@code obj} is a {@code Sequence}, casts it to an unmodifiable {@code List<T>} after
-   * checking that each element has type {@code type}. If {@code obj} is {@code None} or null,
-   * treats it as an empty list. For all other values, throws an {@link EvalException}.
-   *
-   * <p>The returned list may or may not be a view that is affected by updates to the original list.
-   *
-   * @param obj the object to cast. null and None are treated as an empty list.
-   * @param type the expected type of all the list's elements
-   * @param description a description of the argument being converted, or null, for debugging
-   */
-  // TODO(adonovan): this method doesn't belong here.
-  public static <T> List<T> castSkylarkListOrNoneToList(
-      Object obj, Class<T> type, @Nullable String description) throws EvalException {
-    if (EvalUtils.isNullOrNone(obj)) {
-      return ImmutableList.of();
-    }
-    if (obj instanceof Sequence) {
-      return ((Sequence<?>) obj).getContents(type, description);
-    }
-    throw Starlark.errorf(
-        "Illegal argument: %s is not of expected type list or NoneType",
-        description == null ? Starlark.repr(obj) : String.format("'%s'", description));
-  }
-
-  /**
-   * Casts this list as an unmodifiable {@code List<T>}, after checking that each element has type
-   * {@code type}.
-   *
-   * @param type the expected type of all the list's elements
-   * @param description a description of the argument being converted, or null, for debugging
-   */
-  // TODO(adonovan): this method doesn't belong here.
-  default <T> List<T> getContents(Class<T> type, @Nullable String description)
+  /** Like {@link #cast}, but if x is None, returns an immutable empty list. */
+  public static <T> Sequence<T> noneableCast(Object x, Class<T> type, String what)
       throws EvalException {
-    return castList(this, type, description);
+    return x == Starlark.NONE ? StarlarkList.empty() : cast(x, type, what);
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java
index d3e4d83..141ba52 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkType.java
@@ -19,16 +19,13 @@
 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.concurrent.BlazeInterners;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
-import com.google.errorprone.annotations.FormatMethod;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
 import javax.annotation.Nullable;
 
 /**
@@ -595,8 +592,6 @@
     }
   }
 
-  // Utility functions regarding types
-
   private static SkylarkType getGenericArgType(Object value) {
     if (value instanceof Depset) {
       return ((Depset) value).getContentType();
@@ -604,99 +599,4 @@
       return TOP;
     }
   }
-
-  /**
-   * 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 String.format-style format string
-   * @param args - arguments to format, in case there's an exception
-   */
-  // TODO(adonovan): irrelevant; eliminate.
-  @FormatMethod
-  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")
-  @FormatMethod
-  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 Starlark.errorf("got '%s' for '%s', want 'dict'", EvalUtils.getDataTypeName(obj), what);
-    }
-
-    for (Map.Entry<?, ?> input : ((Map<?, ?>) obj).entrySet()) {
-      if (!keyType.isAssignableFrom(input.getKey().getClass())
-          || !valueType.isAssignableFrom(input.getValue().getClass())) {
-        throw Starlark.errorf(
-            "got dict<%s, %s> for '%s', want dict<%s, %s>",
-            Starlark.type(input.getKey()),
-            Starlark.type(input.getValue()),
-            what,
-            EvalUtils.getDataTypeNameFromClass(keyType),
-            EvalUtils.getDataTypeNameFromClass(valueType));
-      }
-    }
-
-    return (Map<KEY_TYPE, VALUE_TYPE>) obj;
-  }
-
-  // TODO(adonovan): eliminate 4 uses outside this package and make it private.
-  // The check is trivial (instanceof) and clients can usually produce a better
-  // error in context, without prematurely constructing a description.
-  public static void checkType(Object object, Class<?> type, @Nullable Object description)
-      throws EvalException {
-    if (!type.isInstance(object)) {
-      throw Starlark.errorf(
-          "expected type '%s' %sbut got type '%s' instead",
-          Starlark.repr(type),
-          description == null ? "" : String.format("for %s ", description),
-          EvalUtils.getDataTypeName(object));
-      }
-  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/StringModule.java b/src/main/java/com/google/devtools/build/lib/syntax/StringModule.java
index 9dd13af..cf7ac6c 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/StringModule.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/StringModule.java
@@ -930,10 +930,7 @@
     if (sub instanceof String) {
       return str.endsWith((String) sub);
     }
-
-    @SuppressWarnings("unchecked")
-    Tuple<Object> subs = (Tuple<Object>) sub;
-    for (String s : subs.getContents(String.class, "string")) {
+    for (String s : Sequence.cast(sub, String.class, "sub")) {
       if (str.endsWith(s)) {
         return true;
       }
@@ -980,7 +977,7 @@
     @SuppressWarnings("unchecked")
     List<Object> argObjects = (List<Object>) args.getImmutableList();
     return new FormatParser()
-        .format(self, argObjects, kwargs.getContents(String.class, Object.class, "kwargs"));
+        .format(self, argObjects, Dict.cast(kwargs, String.class, Object.class, "kwargs"));
   }
 
   @SkylarkCallable(
@@ -1016,10 +1013,7 @@
     if (sub instanceof String) {
       return str.startsWith((String) sub);
     }
-
-    @SuppressWarnings("unchecked")
-    Tuple<Object> subs = (Tuple<Object>) sub;
-    for (String s : subs.getContents(String.class, "string")) {
+    for (String s : Sequence.cast(sub, String.class, "sub")) {
       if (str.startsWith(s)) {
         return true;
       }
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
index 36b7d4d..ecf1f40 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeSkylarkRuleFunctionsApi.java
@@ -29,7 +29,6 @@
 import com.google.devtools.build.lib.syntax.FunctionSignature;
 import com.google.devtools.build.lib.syntax.Location;
 import com.google.devtools.build.lib.syntax.Sequence;
-import com.google.devtools.build.lib.syntax.SkylarkType;
 import com.google.devtools.build.lib.syntax.Starlark;
 import com.google.devtools.build.lib.syntax.StarlarkFunction;
 import com.google.devtools.build.lib.syntax.StarlarkThread;
@@ -92,25 +91,13 @@
     // Field documentation will be output preserving the order in which the fields are listed.
     ImmutableList.Builder<ProviderFieldInfo> providerFieldInfos = ImmutableList.builder();
     if (fields instanceof Sequence) {
-      @SuppressWarnings("unchecked")
-      Sequence<String> fieldNames =
-          (Sequence<String>)
-              SkylarkType.cast(
-                  fields,
-                  Sequence.class,
-                  String.class,
-                  null,
-                  "Expected list of strings or dictionary of string -> string for 'fields'");
-      for (String fieldName : fieldNames) {
-        providerFieldInfos.add(asProviderFieldInfo(fieldName, "(Undocumented)"));
+      for (String name : Sequence.cast(fields, String.class, "fields")) {
+        providerFieldInfos.add(asProviderFieldInfo(name, "(Undocumented)"));
       }
     } else if (fields instanceof Dict) {
-      Map<String, String> dict = SkylarkType.castMap(
-          fields,
-          String.class, String.class,
-          "Expected list of strings or dictionary of string -> string for 'fields'");
-      for (Map.Entry<String, String> fieldEntry : dict.entrySet()) {
-        providerFieldInfos.add(asProviderFieldInfo(fieldEntry.getKey(), fieldEntry.getValue()));
+      for (Map.Entry<String, String> e :
+          Dict.cast(fields, String.class, String.class, "fields").entrySet()) {
+        providerFieldInfos.add(asProviderFieldInfo(e.getKey(), e.getValue()));
       }
     } else {
       // fields is NONE, so there is no field information to add.
@@ -153,8 +140,7 @@
       throws EvalException {
     ImmutableMap.Builder<String, FakeDescriptor> attrsMapBuilder = ImmutableMap.builder();
     if (attrs != null && attrs != Starlark.NONE) {
-      Dict<?, ?> attrsDict = (Dict<?, ?>) attrs;
-      attrsMapBuilder.putAll(attrsDict.getContents(String.class, FakeDescriptor.class, "attrs"));
+      attrsMapBuilder.putAll(Dict.cast(attrs, String.class, FakeDescriptor.class, "attrs"));
     }
 
     attrsMapBuilder.put("name", IMPLICIT_NAME_ATTRIBUTE_DESCRIPTOR);
@@ -206,8 +192,7 @@
     FakeSkylarkAspect fakeAspect = new FakeSkylarkAspect();
     ImmutableMap.Builder<String, FakeDescriptor> attrsMapBuilder = ImmutableMap.builder();
     if (attrs != null && attrs != Starlark.NONE) {
-      Dict<?, ?> attrsDict = (Dict<?, ?>) attrs;
-      attrsMapBuilder.putAll(attrsDict.getContents(String.class, FakeDescriptor.class, "attrs"));
+      attrsMapBuilder.putAll(Dict.cast(attrs, String.class, FakeDescriptor.class, "attrs"));
     }
 
     attrsMapBuilder.put("name", IMPLICIT_NAME_ATTRIBUTE_DESCRIPTOR);
@@ -218,10 +203,10 @@
             .collect(Collectors.toList());
     attrInfos.sort(new AttributeNameComparator());
 
-    List<String> aspectAttrs = new ArrayList<>();
-    if (attributeAspects != null) {
-      aspectAttrs = attributeAspects.getContents(String.class, "aspectAttrs");
-    }
+    List<String> aspectAttrs =
+        attributeAspects != null
+            ? Sequence.cast(attributeAspects, String.class, "aspectAttrs")
+            : new ArrayList<>();
 
     aspectAttrs =
         aspectAttrs.stream().filter(entry -> !entry.startsWith("_")).collect(Collectors.toList());
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/FakeRepositoryModule.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/FakeRepositoryModule.java
index 9749c26..07e900f 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/FakeRepositoryModule.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository/FakeRepositoryModule.java
@@ -63,8 +63,7 @@
     List<AttributeInfo> attrInfos;
     ImmutableMap.Builder<String, FakeDescriptor> attrsMapBuilder = ImmutableMap.builder();
     if (attrs != null && attrs != Starlark.NONE) {
-      Dict<?, ?> attrsDict = (Dict<?, ?>) attrs;
-      attrsMapBuilder.putAll(attrsDict.getContents(String.class, FakeDescriptor.class, "attrs"));
+      attrsMapBuilder.putAll(Dict.cast(attrs, String.class, FakeDescriptor.class, "attrs"));
     }
 
     attrsMapBuilder.put("name", IMPLICIT_NAME_ATTRIBUTE_DESCRIPTOR);
diff --git a/src/test/java/com/google/devtools/build/lib/packages/WorkspaceFactoryTest.java b/src/test/java/com/google/devtools/build/lib/packages/WorkspaceFactoryTest.java
index ad60ded..7d1e676 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/WorkspaceFactoryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/WorkspaceFactoryTest.java
@@ -175,10 +175,10 @@
   @Test
   public void testRepoMappingNotAStringStringDict() throws Exception {
     helper.parse("local_repository(name='foo', path='/foo', repo_mapping=1)");
-    assertThat(helper.getParserError()).contains("got 'int' for 'repo_mapping', want 'dict'");
+    assertThat(helper.getParserError()).contains("got int for 'repo_mapping', want dict");
 
     helper.parse("local_repository(name='foo', path='/foo', repo_mapping='hello')");
-    assertThat(helper.getParserError()).contains("got 'string' for 'repo_mapping', want 'dict'");
+    assertThat(helper.getParserError()).contains("got string for 'repo_mapping', want dict");
 
     helper.parse("local_repository(name='foo', path='/foo', repo_mapping={1: 1})");
     assertThat(helper.getParserError())
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/SkylarkCcCommonTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/SkylarkCcCommonTest.java
index 43efec1..58464d6 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/SkylarkCcCommonTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/SkylarkCcCommonTest.java
@@ -2376,9 +2376,7 @@
     EvalException e =
         assertThrows(
             EvalException.class, () -> CcModule.withFeatureSetFromSkylark(withFeatureSetProvider));
-    assertThat(e)
-        .hasMessageThat()
-        .contains("Illegal argument: 'features' is not of expected type list or NoneType");
+    assertThat(e).hasMessageThat().contains("for features, got struct, want sequence");
   }
 
   @Test
@@ -2392,9 +2390,7 @@
     EvalException e =
         assertThrows(
             EvalException.class, () -> CcModule.withFeatureSetFromSkylark(withFeatureSetProvider));
-    assertThat(e)
-        .hasMessageThat()
-        .contains("Illegal argument: 'not_features' is not of expected type list or NoneType");
+    assertThat(e).hasMessageThat().contains("for not_features, got struct, want sequence");
   }
 
   @Test
@@ -2410,7 +2406,7 @@
             EvalException.class, () -> CcModule.withFeatureSetFromSkylark(withFeatureSetProvider));
     assertThat(e)
         .hasMessageThat()
-        .contains("expected type 'string' for 'features' element but got type 'struct' instead");
+        .contains("at index 0 of features, got element of type struct, want string");
   }
 
   @Test
@@ -2426,8 +2422,7 @@
             EvalException.class, () -> CcModule.withFeatureSetFromSkylark(withFeatureSetProvider));
     assertThat(e)
         .hasMessageThat()
-        .contains(
-            "expected type 'string' for 'not_features' element but got type 'struct' instead");
+        .contains("at index 0 of not_features, got element of type struct, want string");
   }
 
   private void createCustomWithFeatureSetRule(String pkg, String features, String notFeatures)
@@ -2588,9 +2583,7 @@
     assertThat(envSetProvider).isNotNull();
     EvalException e =
         assertThrows(EvalException.class, () -> CcModule.envSetFromSkylark(envSetProvider));
-    assertThat(e)
-        .hasMessageThat()
-        .contains("'env_entries' is not of expected type list or NoneType");
+    assertThat(e).hasMessageThat().contains("for env_entries, got struct, want sequence");
   }
 
   @Test
@@ -2624,9 +2617,7 @@
     assertThat(envSetProvider).isNotNull();
     EvalException e =
         assertThrows(EvalException.class, () -> CcModule.envSetFromSkylark(envSetProvider));
-    assertThat(e)
-        .hasMessageThat()
-        .contains("'with_features' is not of expected type list or NoneType");
+    assertThat(e).hasMessageThat().contains("for with_features, got string, want sequence");
   }
 
   @Test
@@ -3170,9 +3161,7 @@
     EvalException e = assertThrows(EvalException.class, () -> CcModule.toolFromSkylark(toolStruct));
     assertThat(e)
         .hasMessageThat()
-        .contains(
-            "expected type 'string' for 'execution_requirements' "
-                + "element but got type 'struct' instead");
+        .contains("at index 0 of execution_requirements, got element of type struct, want string");
   }
 
   @Test
@@ -3245,9 +3234,7 @@
               SkylarkInfo toolStruct = (SkylarkInfo) getMyInfoFromTarget(t).getValue("tool");
               assertThat(toolStruct).isNotNull();
     EvalException e = assertThrows(EvalException.class, () -> CcModule.toolFromSkylark(toolStruct));
-    assertThat(e)
-        .hasMessageThat()
-        .contains("Illegal argument: 'with_features' is not of expected type list or NoneType");
+    assertThat(e).hasMessageThat().contains("for with_features, got struct, want sequence");
   }
 
   @Test
@@ -3280,8 +3267,7 @@
     EvalException e = assertThrows(EvalException.class, () -> CcModule.toolFromSkylark(toolStruct));
     assertThat(e)
         .hasMessageThat()
-        .contains(
-            "llegal argument: 'execution_requirements' is not of expected type list or NoneType");
+        .contains("for execution_requirements, got string, want sequence");
   }
 
   @Test
@@ -3296,9 +3282,7 @@
     EvalException e = assertThrows(EvalException.class, () -> CcModule.toolFromSkylark(toolStruct));
     assertThat(e)
         .hasMessageThat()
-        .contains(
-            "expected type 'string' for 'execution_requirements' "
-                + "element but got type 'struct' instead");
+        .contains("at index 0 of execution_requirements, got element of type struct, want string");
   }
 
   private void createCustomToolRule(
@@ -3359,7 +3343,7 @@
             () -> CcModule.flagSetFromSkylark(flagSetStruct, /* actionName= */ null));
     assertThat(e)
         .hasMessageThat()
-        .contains("expected type 'string' for 'actions' element but got type 'struct' instead");
+        .contains("at index 0 of actions, got element of type struct, want string");
   }
 
   @Test
@@ -3499,9 +3483,7 @@
         assertThrows(
             EvalException.class,
             () -> CcModule.flagSetFromSkylark(flagSetStruct, /* actionName */ null));
-    assertThat(e)
-        .hasMessageThat()
-        .contains("Illegal argument: 'flag_groups' is not of expected type list or NoneType");
+    assertThat(e).hasMessageThat().contains("for flag_groups, got struct, want sequence");
   }
 
   @Test
@@ -3517,9 +3499,7 @@
         assertThrows(
             EvalException.class,
             () -> CcModule.flagSetFromSkylark(flagSetStruct, /* actionName */ null));
-    assertThat(e)
-        .hasMessageThat()
-        .contains("Illegal argument: 'with_features' is not of expected type list or NoneType");
+    assertThat(e).hasMessageThat().contains("for with_features, got struct, want sequence");
   }
 
   @Test
@@ -3535,9 +3515,7 @@
         assertThrows(
             EvalException.class,
             () -> CcModule.flagSetFromSkylark(flagSetStruct, /* actionName */ null));
-    assertThat(e)
-        .hasMessageThat()
-        .contains("Illegal argument: 'actions' is not of expected type list or NoneType");
+    assertThat(e).hasMessageThat().contains("for actions, got struct, want sequence");
   }
 
   private void createCustomFlagSetRule(
@@ -3669,7 +3647,7 @@
             EvalException.class, () -> CcModule.actionConfigFromSkylark(actionConfigStruct));
     assertThat(e)
         .hasMessageThat()
-        .contains("expected type 'string' for 'implies' element but got type 'struct' instead");
+        .contains("at index 0 of implies, got element of type struct, want string");
   }
 
   @Test
@@ -3691,7 +3669,7 @@
             EvalException.class, () -> CcModule.actionConfigFromSkylark(actionConfigStruct));
     assertThat(e)
         .hasMessageThat()
-        .contains("expected type 'string' for 'implies' element but got type 'struct' instead");
+        .contains("at index 0 of implies, got element of type struct, want string");
   }
 
   @Test
@@ -3843,9 +3821,7 @@
     EvalException e =
         assertThrows(
             EvalException.class, () -> CcModule.actionConfigFromSkylark(actionConfigStruct));
-    assertThat(e)
-        .hasMessageThat()
-        .contains("Illegal argument: 'tools' is not of expected type list or NoneType");
+    assertThat(e).hasMessageThat().contains("for tools, got struct, want sequence");
   }
 
   @Test
@@ -3865,9 +3841,7 @@
     EvalException e =
         assertThrows(
             EvalException.class, () -> CcModule.actionConfigFromSkylark(actionConfigStruct));
-    assertThat(e)
-        .hasMessageThat()
-        .contains("Illegal argument: 'flag_sets' is not of expected type list or NoneType");
+    assertThat(e).hasMessageThat().contains("for flag_sets, got bool, want sequence");
   }
 
   @Test
@@ -3887,9 +3861,7 @@
     EvalException e =
         assertThrows(
             EvalException.class, () -> CcModule.actionConfigFromSkylark(actionConfigStruct));
-    assertThat(e)
-        .hasMessageThat()
-        .contains("Illegal argument: 'implies' is not of expected type list or NoneType");
+    assertThat(e).hasMessageThat().contains("for implies, got struct, want sequence");
   }
 
   private void createCustomActionConfigRule(
@@ -4040,7 +4012,7 @@
         assertThrows(EvalException.class, () -> CcModule.featureFromSkylark(featureStruct));
     assertThat(e)
         .hasMessageThat()
-        .contains("expected type 'string' for 'implies' element but got type 'struct' instead");
+        .contains("at index 0 of implies, got element of type struct, want string");
   }
 
   @Test
@@ -4064,7 +4036,7 @@
         assertThrows(EvalException.class, () -> CcModule.featureFromSkylark(featureStruct));
     assertThat(e)
         .hasMessageThat()
-        .contains("expected type 'string' for 'provides' element but got type 'struct' instead");
+        .contains("at index 0 of provides, got element of type struct, want string");
   }
 
   @Test
@@ -4226,9 +4198,7 @@
     assertThat(featureStruct).isNotNull();
     EvalException e =
         assertThrows(EvalException.class, () -> CcModule.featureFromSkylark(featureStruct));
-    assertThat(e)
-        .hasMessageThat()
-        .contains("Illegal argument: 'flag_sets' is not of expected type list or NoneType");
+    assertThat(e).hasMessageThat().contains("for flag_sets, got struct, want sequence");
   }
 
   @Test
@@ -4249,9 +4219,7 @@
     assertThat(featureStruct).isNotNull();
     EvalException e =
         assertThrows(EvalException.class, () -> CcModule.featureFromSkylark(featureStruct));
-    assertThat(e)
-        .hasMessageThat()
-        .contains("Illegal argument: 'env_sets' is not of expected type list or NoneType");
+    assertThat(e).hasMessageThat().contains("for env_sets, got struct, want sequence");
   }
 
   @Test
@@ -4272,9 +4240,7 @@
     assertThat(featureStruct).isNotNull();
     EvalException e =
         assertThrows(EvalException.class, () -> CcModule.featureFromSkylark(featureStruct));
-    assertThat(e)
-        .hasMessageThat()
-        .contains("Illegal argument: 'requires' is not of expected type list or NoneType");
+    assertThat(e).hasMessageThat().contains("for requires, got struct, want sequence");
   }
 
   @Test
@@ -4295,9 +4261,7 @@
     assertThat(featureStruct).isNotNull();
     EvalException e =
         assertThrows(EvalException.class, () -> CcModule.featureFromSkylark(featureStruct));
-    assertThat(e)
-        .hasMessageThat()
-        .contains("Illegal argument: 'implies' is not of expected type list or NoneType");
+    assertThat(e).hasMessageThat().contains("for implies, got struct, want sequence");
   }
 
   @Test
@@ -4318,9 +4282,7 @@
     assertThat(featureStruct).isNotNull();
     EvalException e =
         assertThrows(EvalException.class, () -> CcModule.featureFromSkylark(featureStruct));
-    assertThat(e)
-        .hasMessageThat()
-        .contains("Illegal argument: 'provides' is not of expected type list or NoneType");
+    assertThat(e).hasMessageThat().contains("for provides, got struct, want sequence");
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/rules/python/PyStructUtilsTest.java b/src/test/java/com/google/devtools/build/lib/rules/python/PyStructUtilsTest.java
index 2fe94e3..b77aadf 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/python/PyStructUtilsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/python/PyStructUtilsTest.java
@@ -91,8 +91,7 @@
       ThrowingRunnable access, String fieldName, String expectedType) {
     assertThrowsEvalExceptionContaining(
         access,
-        String.format(
-            "\'py' provider's '%s' field should be a %s (got a 'int')", fieldName, expectedType));
+        String.format("\'py' provider's '%s' field was int, want %s", fieldName, expectedType));
   }
 
   /** We need this because {@code NestedSet}s don't have value equality. */
@@ -154,7 +153,7 @@
   public void getUsesSharedLibraries_WrongType() {
     StructImpl info = makeStruct(ImmutableMap.of(PyStructUtils.USES_SHARED_LIBRARIES, 123));
     assertHasWrongTypeMessage(
-        () -> PyStructUtils.getUsesSharedLibraries(info), "uses_shared_libraries", "boolean");
+        () -> PyStructUtils.getUsesSharedLibraries(info), "uses_shared_libraries", "bool");
   }
 
   @Test
@@ -191,7 +190,7 @@
   public void getHasPy2OnlySources_WrongType() {
     StructImpl info = makeStruct(ImmutableMap.of(PyStructUtils.HAS_PY2_ONLY_SOURCES, 123));
     assertHasWrongTypeMessage(
-        () -> PyStructUtils.getHasPy2OnlySources(info), "has_py2_only_sources", "boolean");
+        () -> PyStructUtils.getHasPy2OnlySources(info), "has_py2_only_sources", "bool");
   }
 
   @Test
@@ -209,7 +208,7 @@
   public void getHasPy3OnlySources_WrongType() {
     StructImpl info = makeStruct(ImmutableMap.of(PyStructUtils.HAS_PY3_ONLY_SOURCES, 123));
     assertHasWrongTypeMessage(
-        () -> PyStructUtils.getHasPy3OnlySources(info), "has_py3_only_sources", "boolean");
+        () -> PyStructUtils.getHasPy3OnlySources(info), "has_py3_only_sources", "bool");
   }
 
   /** Checks values set by the builder. */
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
index 11ef9a4..8a59f59 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleClassFunctionsTest.java
@@ -401,7 +401,7 @@
   @Test
   public void testLabelListWithAspectsError() throws Exception {
     checkEvalErrorContains(
-        "expected type 'Aspect' for 'aspects' element but got type 'int' instead",
+        "at index 0 of aspects, got element of type int, want Aspect",
         "def _impl(target, ctx):",
         "   pass",
         "my_aspect = aspect(implementation = _impl)",
diff --git a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java
index 65637e4..77bdd6d 100644
--- a/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skylark/SkylarkRuleImplementationFunctionsTest.java
@@ -401,7 +401,7 @@
   public void testCreateSpawnActionBadGenericArg() throws Exception {
     setRuleContext(createRuleContext("//foo:foo"));
     checkEvalErrorContains(
-        "expected type 'File' for 'outputs' element but got type 'string' instead",
+        "at index 0 of outputs, got element of type string, want File",
         "l = ['a', 'b']",
         "ruleContext.actions.run_shell(",
         "  outputs = l,",
@@ -841,7 +841,7 @@
   public void testRunfilesBadListGenericType() throws Exception {
     setRuleContext(createRuleContext("//foo:foo"));
     checkEvalErrorContains(
-        "expected type 'File' for 'files' element but got type 'string' instead",
+        "at index 0 of files, got element of type string, want File",
         "ruleContext.runfiles(files = ['some string'])");
   }
 
@@ -857,16 +857,16 @@
   public void testRunfilesBadMapGenericType() throws Exception {
     setRuleContext(createRuleContext("//foo:foo"));
     checkEvalErrorContains(
-        "expected type 'string' for 'symlinks' key but got type 'int' instead",
+        "got dict<int, File> for 'symlinks', want dict<string, File>",
         "ruleContext.runfiles(symlinks = {123: ruleContext.files.srcs[0]})");
     checkEvalErrorContains(
-        "expected type 'File' for 'symlinks' value but got type 'int' instead",
+        "got dict<string, int> for 'symlinks', want dict<string, File>",
         "ruleContext.runfiles(symlinks = {'some string': 123})");
     checkEvalErrorContains(
-        "expected type 'string' for 'root_symlinks' key but got type 'int' instead",
+        "got dict<int, File> for 'root_symlinks', want dict<string, File>",
         "ruleContext.runfiles(root_symlinks = {123: ruleContext.files.srcs[0]})");
     checkEvalErrorContains(
-        "expected type 'File' for 'root_symlinks' value but got type 'int' instead",
+        "got dict<string, int> for 'root_symlinks', want dict<string, File>",
         "ruleContext.runfiles(root_symlinks = {'some string': 123})");
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/DepsetTest.java b/src/test/java/com/google/devtools/build/lib/syntax/DepsetTest.java
index f1592ca..431cbee 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/DepsetTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/DepsetTest.java
@@ -234,7 +234,7 @@
   public void testItemsAndTransitive() throws Exception {
     new Scenario()
         .testIfExactError(
-            "expected type 'sequence' for items but got type 'depset' instead",
+            "for items, got depset, want sequence",
             "depset(items = depset(), transitive = [depset()])");
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
index 428a7b1..78aeeac 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/MethodLibraryTest.java
@@ -716,9 +716,7 @@
   @Test
   public void testDepsetDirectInvalidType() throws Exception {
     new Scenario()
-        .testIfErrorContains(
-            "expected type 'sequence' for direct but got type 'string' instead",
-            "depset(direct='hello')");
+        .testIfErrorContains("for direct, got string, want sequence", "depset(direct='hello')");
   }
 
   @Test
diff --git a/src/test/starlark/testdata/string_misc.sky b/src/test/starlark/testdata/string_misc.sky
index fd909ed..4ba76e3 100644
--- a/src/test/starlark/testdata/string_misc.sky
+++ b/src/test/starlark/testdata/string_misc.sky
@@ -91,9 +91,9 @@
 ---
 'a'.endswith(['a']) ### got .* 'list', want 'string or tuple of strings
 ---
-'1'.endswith((1,)) ### expected type 'string' for 'string' element but got type 'int' instead
+'1'.endswith((1,)) ### at index 0 of sub, got element of type int, want string
 ---
-'a'.endswith(('1', 1)) ### expected type 'string' for 'string' element but got type 'int' instead
+'a'.endswith(('1', 1)) ### at index 0 of sub, got element of type int, want string
 ---
 
 # startswith
@@ -119,9 +119,9 @@
 ---
 'a'.startswith(['a']) ### got .* 'list', want 'string or tuple of strings'
 ---
-'1'.startswith((1,)) ### expected type 'string' for 'string' element but got type 'int' instead
+'1'.startswith((1,)) ### at index 0 of sub, got element of type int, want string
 ---
-'a'.startswith(('1', 1)) ### expected type 'string' for 'string' element but got type 'int' instead
+'a'.startswith(('1', 1)) ### at index 0 of sub, got element of type int, want string
 ---
 
 # substring