BEGIN_PUBLIC
starlark: infinite precision ints

BEWARE: j.l.Integer is no longer a legal Starlark value.

This change makes the Starlark 'int' type a bigint,
aka infinite-precision integer, one capable of exact arithmetic
on any integer value. The new Int class has three subclasses,
similar to j.l.Integer, j.l.Long, and j.l.BigInteger.
The most compact representation is always used.

This makes Starlark capable of handling all the integer
types that occur in protocol messages---signed and unsigned
64-bit values.

Memory usage should not change much because StarlarkInt.Int32
has the same layout as the j.l.Integer it replaces.
As with j.l.Integer, small values (<100,000) are cached
to avoid unnecessary allocation.

Integer is no longer a legal Starlark value type. However,
for compatibility and convenience, parameters of
StarlarkMethod-annotated Java functions may continue to
use Integer to mean "32-bit signed int". The interpreter
does a "reboxing" operation to convert Int arguments
to Integer parameters as needed. Also, such functions may
return Integer values, and they will be reboxed by fromJava,
similar to List and Map. However, just as one cannot return
List and Map values nested inside Starlark data structures,
nor can one nest Integers in them; an explicit boxing operation
is required: StarlarkInt.of(x).

To limit the scope of this change, it does not yet add support
for parsing bigint literals. That will come in a follow-up.

Bazel: attr.int(..) rule attributes now use Int instead of
Integer. This is necessary because the assumption that Attribute
values are all legal Starlark values seems to be widely relied on.
However, int attributes remain restricted to the signed 32-bit
part of the value range. (Changing this would have much greater
ramifications for Bazel.) Every access of an int-valued rule
attribute must now call toIntUnchecked(), which cannot fail.

Suggested reading order:
- eval.StarlarkInt, the new type.
- the rest of eval, which does reboxing.
- trivial updates to tests of the interpreter.
  (Most of these tests belong in testdata/*.star files.)
- lib.packages attribute changes.
- the rest, which is mostly trivial updates.

The most obvious downsides of this change are the loss of implicit
boxing, the potential for latent errors due to the lack of dynamic
checkValid calls in (e.g.) Dict.put, and the need to tell other Java
packages (such as the Gson JSON package) that Int is basically a
version of Integer, which it already knows about.

I have no doubt missed a few spots, and we may encounter a few
unhelpful "want int, got int" errors when an Integer value
sneaks into the Starlark value realm. I will fix them as they
arise.

Credit to Jon Brandvein for b/36358845#comment9. Before it, I
had resigned myself to this feature being infeasible in Starlark/Java.

END_PUBLIC

PiperOrigin-RevId: 334649352
diff --git a/src/main/java/com/google/devtools/build/docgen/RuleDocumentationAttribute.java b/src/main/java/com/google/devtools/build/docgen/RuleDocumentationAttribute.java
index c0e3396..bdde055 100644
--- a/src/main/java/com/google/devtools/build/docgen/RuleDocumentationAttribute.java
+++ b/src/main/java/com/google/devtools/build/docgen/RuleDocumentationAttribute.java
@@ -27,6 +27,7 @@
 import java.util.LinkedList;
 import java.util.Map;
 import java.util.Set;
+import net.starlark.java.eval.StarlarkInt;
 
 /**
  * A class storing a rule attribute documentation along with some meta information. The class
@@ -176,8 +177,8 @@
     Object value = attribute.getDefaultValueUnchecked();
     if (value instanceof Boolean) {
       return prefix + ((Boolean) value ? "True" : "False");
-    } else if (value instanceof Integer) {
-      return prefix + String.valueOf(value);
+    } else if (value instanceof StarlarkInt) {
+      return prefix + value;
     } else if (value instanceof String && !((String) value).isEmpty()) {
       return prefix + "\"" + value + "\"";
     } else if (value instanceof TriState) {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BUILD b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
index e36237b..c0fe156 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BUILD
@@ -1595,6 +1595,7 @@
         "//src/main/java/com/google/devtools/build/lib/cmdline",
         "//src/main/java/com/google/devtools/build/lib/packages",
         "//src/main/java/com/google/devtools/common/options",
+        "//src/main/java/net/starlark/java/eval",
         "//third_party:guava",
     ],
 )
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java b/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java
index ec3919a..3eace72 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BaseRuleClasses.java
@@ -50,6 +50,7 @@
 import com.google.devtools.build.lib.packages.Type.ConversionException;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.util.FileTypeSet;
+import net.starlark.java.eval.StarlarkInt;
 
 /**
  * Rule class definitions used by (almost) every rule.
@@ -185,7 +186,7 @@
                   .value(false)
                   .taggable()
                   .nonconfigurable("policy decision: should be consistent across configurations"))
-          .add(attr("shard_count", INTEGER).value(-1))
+          .add(attr("shard_count", INTEGER).value(StarlarkInt.of(-1)))
           .add(
               attr("local", BOOLEAN)
                   .value(false)
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
index e8ec586..fc7ad33 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
@@ -378,7 +378,8 @@
   }
 
   private TestProvider initializeTestProvider(FilesToRunProvider filesToRunProvider) {
-    int explicitShardCount = ruleContext.attributes().get("shard_count", Type.INTEGER);
+    int explicitShardCount =
+        ruleContext.attributes().get("shard_count", Type.INTEGER).toIntUnchecked();
     if (explicitShardCount < 0
         && ruleContext.getRule().isAttributeValueExplicitlySpecified("shard_count")) {
       ruleContext.attributeError("shard_count", "Must not be negative.");
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/CoreOptionConverters.java b/src/main/java/com/google/devtools/build/lib/analysis/config/CoreOptionConverters.java
index 1ac9e71..fc76634 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/CoreOptionConverters.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/CoreOptionConverters.java
@@ -30,7 +30,6 @@
 import com.google.devtools.common.options.Converter;
 import com.google.devtools.common.options.Converters.BooleanConverter;
 import com.google.devtools.common.options.Converters.CommaSeparatedOptionListConverter;
-import com.google.devtools.common.options.Converters.IntegerConverter;
 import com.google.devtools.common.options.Converters.StringConverter;
 import com.google.devtools.common.options.EnumConverter;
 import com.google.devtools.common.options.OptionsParsingException;
@@ -38,6 +37,7 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import net.starlark.java.eval.StarlarkInt;
 
 /**
  * {@link Converter}s for {@link com.google.devtools.common.options.Option}s that aren't
@@ -51,7 +51,7 @@
    */
   public static final ImmutableMap<Type<?>, Converter<?>> BUILD_SETTING_CONVERTERS =
       new ImmutableMap.Builder<Type<?>, Converter<?>>()
-          .put(INTEGER, new IntegerConverter())
+          .put(INTEGER, new StarlarkIntConverter())
           .put(BOOLEAN, new BooleanConverter())
           .put(STRING, new StringConverter())
           .put(STRING_LIST, new CommaSeparatedOptionListConverter())
@@ -59,6 +59,26 @@
           .put(LABEL_LIST, new LabelListConverter())
           .build();
 
+  /** A converter from strings to Starlark int values. */
+  private static class StarlarkIntConverter implements Converter<StarlarkInt> {
+    @Override
+    public StarlarkInt convert(String input) throws OptionsParsingException {
+      // Note that Starlark rule attribute values are currently restricted
+      // to the signed 32-bit range, but Starlark-based flags may take on
+      // any integer value.
+      try {
+        return StarlarkInt.parse(input, 0);
+      } catch (NumberFormatException ex) {
+        throw new OptionsParsingException("invalid int: " + ex.getMessage());
+      }
+    }
+
+    @Override
+    public String getTypeDescription() {
+      return "an int";
+    }
+  }
+
   /** A converter from strings to Labels. */
   public static class LabelConverter implements Converter<Label> {
     @Override
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/Args.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/Args.java
index 3db934f..cd8e208 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/Args.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/Args.java
@@ -435,8 +435,7 @@
     private void validateArgName(Object argName) throws EvalException {
       if (!(argName instanceof String)) {
         throw Starlark.errorf(
-            "expected value of type 'string' for arg name, got '%s'",
-            argName.getClass().getSimpleName());
+            "expected value of type 'string' for arg name, got '%s'", Starlark.type(argName));
       }
     }
 
@@ -444,7 +443,7 @@
       if (!(values instanceof Sequence || values instanceof Depset)) {
         throw Starlark.errorf(
             "expected value of type 'sequence or depset' for values, got '%s'",
-            values.getClass().getSimpleName());
+            Starlark.type(values));
       }
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java
index 9618721..28a52a5 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkAttrModule.java
@@ -55,6 +55,7 @@
 import net.starlark.java.eval.Sequence;
 import net.starlark.java.eval.Starlark;
 import net.starlark.java.eval.StarlarkFunction;
+import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.eval.StarlarkThread;
 
 /**
@@ -400,7 +401,7 @@
 
   @Override
   public Descriptor intAttribute(
-      Integer defaultValue,
+      StarlarkInt defaultValue,
       String doc,
       Boolean mandatory,
       Sequence<?> values,
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkCustomCommandLine.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkCustomCommandLine.java
index 4d95f4c..1387477 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkCustomCommandLine.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkCustomCommandLine.java
@@ -860,14 +860,14 @@
               throw new CommandLineExpansionException(
                   "Expected map_each to return string, None, or list of strings, "
                       + "found list containing "
-                      + val.getClass().getSimpleName());
+                      + Starlark.type(val));
             }
             consumer.accept((String) val);
           }
         } else if (ret != Starlark.NONE) {
           throw new CommandLineExpansionException(
               "Expected map_each to return string, None, or list of strings, found "
-                  + ret.getClass().getSimpleName());
+                  + Starlark.type(ret));
         }
       }
     } catch (EvalException e) {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java
index 9201975..bfb3a1e 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java
@@ -97,6 +97,7 @@
 import net.starlark.java.eval.Starlark;
 import net.starlark.java.eval.StarlarkCallable;
 import net.starlark.java.eval.StarlarkFunction;
+import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.eval.StarlarkThread;
 import net.starlark.java.eval.Tuple;
 import net.starlark.java.syntax.Identifier;
@@ -177,7 +178,7 @@
                 .value(false)
                 .taggable()
                 .nonconfigurable("taggable - called in Rule.getRuleTags"))
-        .add(attr("shard_count", INTEGER).value(-1))
+        .add(attr("shard_count", INTEGER).value(StarlarkInt.of(-1)))
         .add(
             attr("local", BOOLEAN)
                 .value(false)
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidSdkRepositoryFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidSdkRepositoryFunction.java
index 192c185..8d794a6 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidSdkRepositoryFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidSdkRepositoryFunction.java
@@ -136,7 +136,7 @@
     Integer defaultApiLevel;
     if (attributes.isAttributeValueExplicitlySpecified("api_level")) {
       try {
-        defaultApiLevel = attributes.get("api_level", Type.INTEGER);
+        defaultApiLevel = attributes.get("api_level", Type.INTEGER).toIntUnchecked();
       } catch (EvalException e) {
         throw new RepositoryFunctionException(e, Transience.PERSISTENT);
       }
diff --git a/src/main/java/com/google/devtools/build/lib/collect/nestedset/Depset.java b/src/main/java/com/google/devtools/build/lib/collect/nestedset/Depset.java
index 1f3efa2..97c6456 100644
--- a/src/main/java/com/google/devtools/build/lib/collect/nestedset/Depset.java
+++ b/src/main/java/com/google/devtools/build/lib/collect/nestedset/Depset.java
@@ -31,6 +31,7 @@
 import net.starlark.java.eval.Printer;
 import net.starlark.java.eval.Sequence;
 import net.starlark.java.eval.Starlark;
+import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.eval.StarlarkList;
 import net.starlark.java.eval.StarlarkSemantics;
 import net.starlark.java.eval.StarlarkThread;
@@ -407,7 +408,7 @@
    * A ElementType represents the type of elements in a Depset.
    *
    * <p>Call {@link #of} to obtain the ElementType for a Java class. The class must be a legal
-   * Starlark value class, such as String, Integer, Boolean, or a subclass of StarlarkValue.
+   * Starlark value class, such as String, Boolean, or a subclass of StarlarkValue.
    *
    * <p>An element type represents only the top-most type identifier of an element value. That is,
    * an element type may represent "list" but not "list of string".
@@ -444,16 +445,23 @@
       return new ElementType(getTypeClass(cls));
     }
 
-    // Returns the Java class representing the Starlark type of an instance of cls,
-    // which must be one of String, Integer, or Boolean (in which case the result is cls),
-    // or a StarlarkModule-annotated Starlark value class or one of its subclasses,
-    // in which case the result is the annotated class.
+    // If cls is a valid Starlark type, returns the canonical Java class for that
+    // Starlark type (which may be an ancestor); otherwise throws IllegalArgumentException.
+    //
+    // If cls is String or Boolean, cls is returned. Otherwise, the
+    // @StarlarkBuiltin-annotated ancestor of cls is returned if it exists (it may
+    // be cls itself), or cls is returned if there is no such ancestor.
     //
     // TODO(adonovan): consider publishing something like this as Starlark.typeClass.
     private static Class<?> getTypeClass(Class<?> cls) {
-      if (cls == String.class || cls == Integer.class || cls == Boolean.class) {
+      if (cls == String.class || cls == Boolean.class) {
         return cls; // fast path for common case
       }
+      if (cls == StarlarkInt.class) {
+        // StarlarkInt doesn't currently have a StarlarkBuiltin annotation
+        // because stardoc can't handle a type and a function with the same name.
+        return cls;
+      }
       Class<?> superclass = StarlarkInterfaceUtils.getParentWithStarlarkBuiltin(cls);
       if (superclass != null) {
         return superclass;
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Attribute.java b/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
index 1458585..3bc4a89 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Attribute.java
@@ -285,6 +285,9 @@
     public AllowedValueSet(Iterable<?> values) {
       Preconditions.checkNotNull(values);
       Preconditions.checkArgument(!Iterables.isEmpty(values));
+      for (Object v : values) {
+        Starlark.checkValid(v);
+      }
       allowedValues = ImmutableSet.copyOf(values);
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/packages/AttributeFormatter.java b/src/main/java/com/google/devtools/build/lib/packages/AttributeFormatter.java
index 19a3a81..28df7b5 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/AttributeFormatter.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/AttributeFormatter.java
@@ -51,6 +51,7 @@
 import java.util.List;
 import java.util.Map;
 import javax.annotation.Nullable;
+import net.starlark.java.eval.StarlarkInt;
 
 /** Common utilities for serializing {@link Attribute}s as protocol buffers. */
 public class AttributeFormatter {
@@ -168,11 +169,10 @@
    * Set the appropriate type and value. Since string and string list store values for multiple
    * types, use the toString() method on the objects instead of casting them.
    */
-  @SuppressWarnings("unchecked")
   private static void writeAttributeValueToBuilder(
       AttributeValueBuilderAdapter builder, Type<?> type, Object value) {
     if (type == INTEGER) {
-      builder.setIntValue((Integer) value);
+      builder.setIntValue(((StarlarkInt) value).toIntUnchecked());
     } else if (type == STRING || type == LABEL || type == NODEP_LABEL || type == OUTPUT) {
       builder.setStringValue(value.toString());
     } else if (type == STRING_LIST || type == LABEL_LIST || type == NODEP_LABEL_LIST
@@ -181,8 +181,8 @@
         builder.addStringListValue(entry.toString());
       }
     } else if (type == INTEGER_LIST) {
-      for (Integer entry : (Collection<Integer>) value) {
-        builder.addIntListValue(entry);
+      for (Object elem : (Collection<?>) value) {
+        builder.addIntListValue(((StarlarkInt) elem).toIntUnchecked());
       }
     } else if (type == BOOLEAN) {
       builder.setBooleanValue((Boolean) value);
@@ -199,6 +199,7 @@
       }
       builder.setLicense(licensePb);
     } else if (type == STRING_DICT) {
+      @SuppressWarnings("unchecked")
       Map<String, String> dict = (Map<String, String>) value;
       for (Map.Entry<String, String> keyValueList : dict.entrySet()) {
         StringDictEntry.Builder entry =
@@ -208,6 +209,7 @@
         builder.addStringDictValue(entry);
       }
     } else if (type == STRING_LIST_DICT) {
+      @SuppressWarnings("unchecked")
       Map<String, List<String>> dict = (Map<String, List<String>>) value;
       for (Map.Entry<String, List<String>> dictEntry : dict.entrySet()) {
         StringListDictEntry.Builder entry =
@@ -218,6 +220,7 @@
         builder.addStringListDictValue(entry);
       }
     } else if (type == LABEL_DICT_UNARY) {
+      @SuppressWarnings("unchecked")
       Map<String, Label> dict = (Map<String, Label>) value;
       for (Map.Entry<String, Label> dictEntry : dict.entrySet()) {
         LabelDictUnaryEntry.Builder entry =
@@ -227,6 +230,7 @@
         builder.addLabelDictUnaryValue(entry);
       }
     } else if (type == LABEL_KEYED_STRING_DICT) {
+      @SuppressWarnings("unchecked")
       Map<Label, String> dict = (Map<Label, String>) value;
       for (Map.Entry<Label, String> dictEntry : dict.entrySet()) {
         LabelKeyedStringDictEntry.Builder entry =
@@ -236,6 +240,7 @@
         builder.addLabelKeyedStringDictValue(entry);
       }
     } else if (type == FILESET_ENTRY_LIST) {
+      @SuppressWarnings("unchecked")
       List<FilesetEntry> filesetEntries = (List<FilesetEntry>) value;
       for (FilesetEntry filesetEntry : filesetEntries) {
         Build.FilesetEntry.Builder filesetEntryPb =
diff --git a/src/main/java/com/google/devtools/build/lib/packages/BuildType.java b/src/main/java/com/google/devtools/build/lib/packages/BuildType.java
index 1356ed1..b3a9953 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/BuildType.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/BuildType.java
@@ -766,7 +766,7 @@
         //       + "instead, use 0 or 1, or None for the default)");
         return ((Boolean) x) ? TriState.YES : TriState.NO;
       }
-      Integer xAsInteger = INTEGER.convert(x, what, context);
+      int xAsInteger = INTEGER.convert(x, what, context).toIntUnchecked();
       if (xAsInteger == -1) {
         return TriState.AUTO;
       } else if (xAsInteger == 1) {
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 ec675f8..26af19b 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
@@ -29,6 +29,7 @@
 import net.starlark.java.eval.Printer;
 import net.starlark.java.eval.Sequence;
 import net.starlark.java.eval.Starlark;
+import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.syntax.Location;
 
 /**
@@ -208,7 +209,7 @@
           sb,
           key + ": \"" + escapeDoubleQuotesAndBackslashesAndNewlines((String) value) + "\"",
           indent);
-    } else if (value instanceof Integer) {
+    } else if (value instanceof StarlarkInt) {
       print(sb, key + ": " + value, indent);
     } else if (value instanceof Boolean) {
       // We're relying on the fact that Java converts Booleans to Strings in the same way
@@ -306,7 +307,7 @@
       sb.append("]");
     } else if (value instanceof String) {
       appendJSONStringLiteral(sb, (String) value);
-    } else if (value instanceof Integer || value instanceof Boolean) {
+    } else if (value instanceof StarlarkInt || value instanceof Boolean) {
       sb.append(value);
     } else {
       throw Starlark.errorf(
diff --git a/src/main/java/com/google/devtools/build/lib/packages/Type.java b/src/main/java/com/google/devtools/build/lib/packages/Type.java
index 0a734ad..6d91b59 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/Type.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/Type.java
@@ -38,6 +38,7 @@
 import net.starlark.java.eval.Printer;
 import net.starlark.java.eval.Sequence;
 import net.starlark.java.eval.Starlark;
+import net.starlark.java.eval.StarlarkInt;
 
 /**
  * Root of Type symbol hierarchy for values in the build language.
@@ -200,10 +201,12 @@
   }
 
   /**
-   * Implementation of concatenation for this type (e.g. "val1 + val2"). Returns null to
-   * indicate concatenation isn't supported.
+   * Implementation of concatenation for this type, as if by {@code elements[0] + ... +
+   * elements[n-1]}). Returns null to indicate concatenation isn't supported. This method exists to
+   * support deferred additions {@code select + T} for catenable types T such as string, int, and
+   * list.
    */
-  public T concat(@SuppressWarnings("unused") Iterable<T> elements) {
+  public T concat(Iterable<T> elements) {
     return null;
   }
 
@@ -221,8 +224,8 @@
     throw new UnsupportedOperationException(msg);
   }
 
-  /** The type of an integer. */
-  @AutoCodec public static final Type<Integer> INTEGER = new IntegerType();
+  /** The type of a Starlark integer in the signed 32-bit range. */
+  @AutoCodec public static final Type<StarlarkInt> INTEGER = new IntegerType();
 
   /** The type of a string. */
   @AutoCodec public static final Type<String> STRING = new StringType();
@@ -233,11 +236,11 @@
   /** The type of a list of not-yet-typed objects. */
   @AutoCodec public static final ObjectListType OBJECT_LIST = new ObjectListType();
 
-  /** The type of a list of {@linkplain #STRING strings}. */
+  /** The type of a list of strings. */
   @AutoCodec public static final ListType<String> STRING_LIST = ListType.create(STRING);
 
-  /** The type of a list of {@linkplain #INTEGER strings}. */
-  @AutoCodec public static final ListType<Integer> INTEGER_LIST = ListType.create(INTEGER);
+  /** The type of a list of signed 32-bit Starlark integer values. */
+  @AutoCodec public static final ListType<StarlarkInt> INTEGER_LIST = ListType.create(INTEGER);
 
   /** The type of a dictionary of {@linkplain #STRING strings}. */
   @AutoCodec
@@ -317,15 +320,18 @@
     }
   }
 
-  private static class IntegerType extends Type<Integer> {
+  // A Starlark integer in the signed 32-bit range (like Java int).
+  private static class IntegerType extends Type<StarlarkInt> {
     @Override
-    public Integer cast(Object value) {
-      return (Integer) value;
+    public StarlarkInt cast(Object value) {
+      // This cast will fail if passed a java.lang.Integer,
+      // as it is not a legal Starlark value. Use StarlarkInt.
+      return (StarlarkInt) value;
     }
 
     @Override
-    public Integer getDefaultValue() {
-      return 0;
+    public StarlarkInt getDefaultValue() {
+      return StarlarkInt.of(0);
     }
 
     @Override
@@ -338,21 +344,36 @@
     }
 
     @Override
-    public Integer convert(Object x, Object what, Object context)
-        throws ConversionException {
-      if (!(x instanceof Integer)) {
-        throw new ConversionException(this, x, what);
+    public StarlarkInt convert(Object x, Object what, Object context) throws ConversionException {
+      if (x instanceof StarlarkInt) {
+        StarlarkInt i = (StarlarkInt) x;
+        try {
+          i.toIntUnchecked(); // assert signed 32-bit
+        } catch (
+            @SuppressWarnings("UnusedException")
+            IllegalArgumentException ex) {
+          String prefix = what != null ? ("for " + what + ", ") : "";
+          throw new ConversionException(
+              String.format("%sgot %s, want value in signed 32-bit range", prefix, i));
+        }
+        return i;
       }
-      return (Integer) x;
+      if (x instanceof Integer) {
+        throw new IllegalArgumentException("Integer is not a legal Starlark value");
+      }
+      throw new ConversionException(this, x, what);
     }
 
     @Override
-    public Integer concat(Iterable<Integer> elements) {
-      int ans = 0;
-      for (Integer elem : elements) {
-        ans += elem;
+    public StarlarkInt concat(Iterable<StarlarkInt> elements) {
+      StarlarkInt sum = StarlarkInt.of(0);
+      for (StarlarkInt elem : elements) {
+        sum = StarlarkInt.add(sum, elem);
       }
-      return Integer.valueOf(ans);
+      // Perform narrowing conversion to ensure that the result
+      // remains in the signed 32-bit range. This means that
+      // s=select(0x7fffffff); s+s may yield a negative result.
+      return StarlarkInt.of(sum.truncateToInt());
     }
   }
 
@@ -383,7 +404,7 @@
       if (x instanceof Boolean) {
         return (Boolean) x;
       }
-      Integer xAsInteger = INTEGER.convert(x, what, context);
+      int xAsInteger = INTEGER.convert(x, what, context).toIntUnchecked();
       if (xAsInteger == 0) {
         return false;
       } else if (xAsInteger == 1) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
index bad9b7f..95b66fd 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java
@@ -90,6 +90,7 @@
 import java.util.Map;
 import java.util.Objects;
 import javax.annotation.Nullable;
+import net.starlark.java.eval.StarlarkInt;
 
 /** An implementation for the "android_binary" rule. */
 public abstract class AndroidBinary implements RuleConfiguredTargetFactory {
@@ -807,10 +808,12 @@
   @Nullable
   private static Integer getProguardOptimizationPasses(RuleContext ruleContext) {
     if (ruleContext.attributes().has("proguard_optimization_passes", Type.INTEGER)) {
-      return ruleContext.attributes().get("proguard_optimization_passes", Type.INTEGER);
-    } else {
-      return null;
+      StarlarkInt i = ruleContext.attributes().get("proguard_optimization_passes", Type.INTEGER);
+      if (i != null) {
+        return i.toIntUnchecked();
+      }
     }
+    return null;
   }
 
   private static ProguardOutput createEmptyProguardAction(
@@ -1039,7 +1042,7 @@
               + "\" not supported by this version of the Android SDK");
     }
 
-    int dexShards = ruleContext.attributes().get("dex_shards", Type.INTEGER);
+    int dexShards = ruleContext.attributes().get("dex_shards", Type.INTEGER).toIntUnchecked();
     if (dexShards > 1) {
       if (multidexMode == MultidexMode.OFF) {
         ruleContext.throwWithRuleError(".dex sharding is only available in multidex mode");
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDevice.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDevice.java
index 7d32a0d..677c489 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDevice.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidDevice.java
@@ -202,12 +202,14 @@
         RuleContext ruleContext, ImmutableMap<String, String> executionInfo) {
       this.ruleContext = ruleContext;
       this.constraints = executionInfo;
-      horizontalResolution = ruleContext.attributes().get("horizontal_resolution", Type.INTEGER);
-      verticalResolution = ruleContext.attributes().get("vertical_resolution", Type.INTEGER);
-      ram = ruleContext.attributes().get("ram", Type.INTEGER);
-      density = ruleContext.attributes().get("screen_density", Type.INTEGER);
-      cache = ruleContext.attributes().get("cache", Type.INTEGER);
-      vmHeap = ruleContext.attributes().get("vm_heap", Type.INTEGER);
+      horizontalResolution =
+          ruleContext.attributes().get("horizontal_resolution", Type.INTEGER).toIntUnchecked();
+      verticalResolution =
+          ruleContext.attributes().get("vertical_resolution", Type.INTEGER).toIntUnchecked();
+      ram = ruleContext.attributes().get("ram", Type.INTEGER).toIntUnchecked();
+      density = ruleContext.attributes().get("screen_density", Type.INTEGER).toIntUnchecked();
+      cache = ruleContext.attributes().get("cache", Type.INTEGER).toIntUnchecked();
+      vmHeap = ruleContext.attributes().get("vm_heap", Type.INTEGER).toIntUnchecked();
 
       defaultProperties =
           Optional.fromNullable(ruleContext.getPrerequisiteArtifact("default_properties"));
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
index a0f3190..4ec66bb 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
@@ -70,6 +70,7 @@
 import com.google.devtools.build.lib.util.FileTypeSet;
 import java.util.List;
 import net.starlark.java.eval.Printer;
+import net.starlark.java.eval.StarlarkInt;
 
 /** Rule definitions for Android rules. */
 public final class AndroidRuleClasses {
@@ -804,7 +805,7 @@
           Note that each shard will result in at least one dex in the final app. For this reason,
           setting this to more than 1 is not recommended for release binaries.
           <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
-          .add(attr("dex_shards", INTEGER).value(1))
+          .add(attr("dex_shards", INTEGER).value(StarlarkInt.of(1)))
           /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(incremental_dexing) -->
           Force the target to be built with or without incremental dexing, overriding defaults
           and --incremental_dexing flag.
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/info/BuildLanguageInfoItem.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/info/BuildLanguageInfoItem.java
index 8ba63d3..98cf8ca 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/info/BuildLanguageInfoItem.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/info/BuildLanguageInfoItem.java
@@ -41,6 +41,7 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import net.starlark.java.eval.StarlarkInt;
 
 /**
  * Info item for the build language. It is deprecated, it still works, when explicitly requested,
@@ -134,7 +135,7 @@
     } else if (t == Type.STRING) {
       b.setString((String) v);
     } else if (t == Type.INTEGER) {
-      b.setInt((Integer) v);
+      b.setInt(((StarlarkInt) v).toIntUnchecked());
     } else if (t == Type.BOOLEAN) {
       b.setBool((Boolean) v);
     } else if (t == BuildType.TRISTATE) {
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkAttrModuleApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkAttrModuleApi.java
index 4f18029..5829b3e 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkAttrModuleApi.java
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkAttrModuleApi.java
@@ -25,6 +25,7 @@
 import net.starlark.java.eval.EvalException;
 import net.starlark.java.eval.Sequence;
 import net.starlark.java.eval.StarlarkFunction;
+import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.eval.StarlarkThread;
 import net.starlark.java.eval.StarlarkValue;
 
@@ -139,11 +140,13 @@
 
   @StarlarkMethod(
       name = "int",
-      doc = "Creates a schema for an integer attribute.",
+      doc =
+          "Creates a schema for an integer attribute. The value must be in the signed 32-bit"
+              + " range.",
       parameters = {
         @Param(
             name = DEFAULT_ARG,
-            type = Integer.class,
+            type = StarlarkInt.class,
             defaultValue = "0",
             doc = DEFAULT_DOC,
             named = true,
@@ -165,7 +168,7 @@
         @Param(
             name = VALUES_ARG,
             type = Sequence.class,
-            generic1 = Integer.class,
+            generic1 = StarlarkInt.class,
             defaultValue = "[]",
             doc = VALUES_DOC,
             named = true,
@@ -173,7 +176,7 @@
       },
       useStarlarkThread = true)
   Descriptor intAttribute(
-      Integer defaultValue,
+      StarlarkInt defaultValue,
       String doc,
       Boolean mandatory,
       Sequence<?> values,
@@ -391,7 +394,9 @@
 
   @StarlarkMethod(
       name = "int_list",
-      doc = "Creates a schema for a list-of-integers attribute.",
+      doc =
+          "Creates a schema for a list-of-integers attribute. Each element must be in the signed"
+              + " 32-bit range.",
       parameters = {
         @Param(
             name = MANDATORY_ARG,
@@ -408,7 +413,7 @@
         @Param(
             name = DEFAULT_ARG,
             type = Sequence.class,
-            generic1 = Integer.class,
+            generic1 = StarlarkInt.class,
             defaultValue = "[]",
             doc = DEFAULT_DOC,
             named = true,
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkdebug/server/DebuggerSerialization.java b/src/main/java/com/google/devtools/build/lib/starlarkdebug/server/DebuggerSerialization.java
index c0f17d3..1ef51b9 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkdebug/server/DebuggerSerialization.java
+++ b/src/main/java/com/google/devtools/build/lib/starlarkdebug/server/DebuggerSerialization.java
@@ -25,6 +25,7 @@
 import net.starlark.java.eval.Debug;
 import net.starlark.java.eval.EvalException;
 import net.starlark.java.eval.Starlark;
+import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.eval.StarlarkSemantics;
 import net.starlark.java.eval.StarlarkValue;
 
@@ -67,6 +68,9 @@
     if (value instanceof Debug.ValueWithDebugAttributes) {
       return true;
     }
+    if (value instanceof StarlarkInt) {
+      return false;
+    }
     if (value instanceof ClassObject || value instanceof StarlarkValue) {
       // assuming ClassObject's have at least one child as a temporary optimization
       // TODO(bazel-team): remove once child-listing logic is moved to StarlarkValue
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkAttrModuleApi.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkAttrModuleApi.java
index bbe8707..a854a93 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkAttrModuleApi.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkAttrModuleApi.java
@@ -27,6 +27,7 @@
 import net.starlark.java.eval.Module;
 import net.starlark.java.eval.Printer;
 import net.starlark.java.eval.Sequence;
+import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.eval.StarlarkThread;
 
 /**
@@ -36,7 +37,7 @@
 
   @Override
   public Descriptor intAttribute(
-      Integer defaultInt,
+      StarlarkInt defaultInt,
       String doc,
       Boolean mandatory,
       Sequence<?> values,
diff --git a/src/main/java/net/starlark/java/annot/StarlarkMethod.java b/src/main/java/net/starlark/java/annot/StarlarkMethod.java
index ac89d58..c1f7a40 100644
--- a/src/main/java/net/starlark/java/annot/StarlarkMethod.java
+++ b/src/main/java/net/starlark/java/annot/StarlarkMethod.java
@@ -63,6 +63,15 @@
  *   <li>Noneable parameter variables must be declared with type Object, as the actual value may be
  *       either {@code None} or some other value, which do not share a superclass other than Object
  *       (or StarlarkValue, which is typically no more descriptive than Object).
+ *   <li>A parameter may have type Integer, or have a {@link Param#type} or {@link
+ *       Param#allowedTypes} annotation that includes Integer.class, even though Integer is not a
+ *       legal Starlark value type. When called from Starlark code, such a parameter accepts
+ *       StarlarkInt values, but only in the signed 32-bit value range. These values are "reboxed"
+ *       to Integer. Note that reboxing will still occur in the case where the annotation-declared
+ *       types include Integer.class but the parameter's type is Object. This means the parameter's
+ *       value cannot be assumed to be a legal Starlark value and should not be freely passed to
+ *       other functions in the Starlark API that require a legal value. To avoid the range and
+ *       legality limitations of reboxing, simply use StarlarkInt in place of Integer.
  *   <li>Parameter variables whose class is generic must be declared using wildcard types. For
  *       example, {@code Sequence<?>} is allowed but {@code Sequence<String>} is forbidden. This is
  *       because the call-time dynamic checks verify the class but cannot verify the type
diff --git a/src/main/java/net/starlark/java/eval/BUILD b/src/main/java/net/starlark/java/eval/BUILD
index 63c549f..f5b198e 100644
--- a/src/main/java/net/starlark/java/eval/BUILD
+++ b/src/main/java/net/starlark/java/eval/BUILD
@@ -40,6 +40,7 @@
         "StarlarkCallable.java",
         "StarlarkFunction.java",
         "StarlarkIndexable.java",
+        "StarlarkInt.java",
         "StarlarkIterable.java",
         "StarlarkList.java",
         "StarlarkSemantics.java",
diff --git a/src/main/java/net/starlark/java/eval/BuiltinCallable.java b/src/main/java/net/starlark/java/eval/BuiltinCallable.java
index a3f1342..8410c8e 100644
--- a/src/main/java/net/starlark/java/eval/BuiltinCallable.java
+++ b/src/main/java/net/starlark/java/eval/BuiltinCallable.java
@@ -185,6 +185,7 @@
       }
 
       Object value = positional[argIndex++];
+      value = param.reboxIntMaybe(value);
       checkParamValue(param, value);
       vector[paramIndex] = value;
     }
@@ -267,6 +268,7 @@
         continue;
       }
 
+      value = param.reboxIntMaybe(value);
       checkParamValue(param, value);
 
       // duplicate?
diff --git a/src/main/java/net/starlark/java/eval/Eval.java b/src/main/java/net/starlark/java/eval/Eval.java
index aa41bee..2791b9c 100644
--- a/src/main/java/net/starlark/java/eval/Eval.java
+++ b/src/main/java/net/starlark/java/eval/Eval.java
@@ -449,7 +449,10 @@
       case INDEX:
         return evalIndex(fr, (IndexExpression) expr);
       case INTEGER_LITERAL:
-        return ((IntegerLiteral) expr).getValue();
+        // TODO(adonovan): opt: avoid allocation by saving
+        // the StarlarkInt in the IntegerLiteral (a temporary hack
+        // until we use a compiled representation).
+        return StarlarkInt.of(((IntegerLiteral) expr).getValue());
       case LIST_EXPR:
         return evalList(fr, (ListExpression) expr);
       case SLICE:
diff --git a/src/main/java/net/starlark/java/eval/EvalUtils.java b/src/main/java/net/starlark/java/eval/EvalUtils.java
index ed32d7e..062548d 100644
--- a/src/main/java/net/starlark/java/eval/EvalUtils.java
+++ b/src/main/java/net/starlark/java/eval/EvalUtils.java
@@ -47,6 +47,7 @@
       new Ordering<Object>() {
         private int compareLists(Sequence<?> o1, Sequence<?> o2) {
           if (o1 instanceof RangeList || o2 instanceof RangeList) {
+            // RangeLists don't support ordered comparison, only equality.
             throw new ComparisonException("Cannot compare range objects");
           }
 
@@ -68,8 +69,9 @@
           if (o1 instanceof String && o2 instanceof String) {
             return ((String) o1).compareTo((String) o2);
           }
-          if (o1 instanceof Integer && o2 instanceof Integer) {
-            return Integer.compare((Integer) o1, (Integer) o2);
+          if (o1 instanceof StarlarkInt && o2 instanceof StarlarkInt) {
+            // int <=> int
+            return StarlarkInt.compare((StarlarkInt) o1, (StarlarkInt) o2);
           }
 
           o1 = Starlark.fromJava(o1, null);
@@ -186,19 +188,10 @@
       throws EvalException {
     switch (op) {
       case PLUS:
-        if (x instanceof Integer) {
-          if (y instanceof Integer) {
+        if (x instanceof StarlarkInt) {
+          if (y instanceof StarlarkInt) {
             // int + int
-            int xi = (Integer) x;
-            int yi = (Integer) y;
-            int z = xi + yi;
-            // Overflow Detection, §2-13 Hacker's Delight:
-            // "operands have the same sign and the sum
-            // has a sign opposite to that of the operands."
-            if (((xi ^ z) & (yi ^ z)) < 0) {
-              throw Starlark.errorf("integer overflow in addition");
-            }
-            return z;
+            return StarlarkInt.add((StarlarkInt) x, (StarlarkInt) y);
           }
 
         } else if (x instanceof String) {
@@ -223,81 +216,55 @@
         break;
 
       case PIPE:
-        if (x instanceof Integer) {
-          if (y instanceof Integer) {
+        if (x instanceof StarlarkInt) {
+          if (y instanceof StarlarkInt) {
             // int | int
-            return ((Integer) x) | (Integer) y;
+            return StarlarkInt.or((StarlarkInt) x, (StarlarkInt) y);
           }
         }
         break;
 
       case AMPERSAND:
-        if (x instanceof Integer && y instanceof Integer) {
+        if (x instanceof StarlarkInt && y instanceof StarlarkInt) {
           // int & int
-          return (Integer) x & (Integer) y;
+          return StarlarkInt.and((StarlarkInt) x, (StarlarkInt) y);
         }
         break;
 
       case CARET:
-        if (x instanceof Integer && y instanceof Integer) {
+        if (x instanceof StarlarkInt && y instanceof StarlarkInt) {
           // int ^ int
-          return (Integer) x ^ (Integer) y;
+          return StarlarkInt.xor((StarlarkInt) x, (StarlarkInt) y);
         }
         break;
 
       case GREATER_GREATER:
-        if (x instanceof Integer && y instanceof Integer) {
-          // int >> int
-          int xi = (Integer) x;
-          int yi = (Integer) y;
-          if (yi < 0) {
-            throw Starlark.errorf("negative shift count: %d", yi);
-          } else if (yi >= Integer.SIZE) {
-            return xi < 0 ? -1 : 0;
-          }
-          return xi >> yi;
+        if (x instanceof StarlarkInt && y instanceof StarlarkInt) {
+          // x >> y
+          return StarlarkInt.shiftRight((StarlarkInt) x, (StarlarkInt) y);
         }
         break;
 
       case LESS_LESS:
-        if (x instanceof Integer && y instanceof Integer) {
-          // int << int
-          int xi = (Integer) x;
-          int yi = (Integer) y;
-          if (yi < 0) {
-            throw Starlark.errorf("negative shift count: %d", yi);
-          }
-          int z = xi << yi; // only uses low 5 bits of yi
-          if ((z >> yi) != xi || yi >= 32) {
-            throw Starlark.errorf("integer overflow in left shift");
-          }
-          return z;
+        if (x instanceof StarlarkInt && y instanceof StarlarkInt) {
+          // x << y
+          return StarlarkInt.shiftLeft((StarlarkInt) x, (StarlarkInt) y);
         }
         break;
 
       case MINUS:
-        if (x instanceof Integer && y instanceof Integer) {
-          // int - int
-          int xi = (Integer) x;
-          int yi = (Integer) y;
-          int z = xi - yi;
-          if (((xi ^ yi) & (xi ^ z)) < 0) {
-            throw Starlark.errorf("integer overflow in subtraction");
-          }
-          return z;
+        if (x instanceof StarlarkInt && y instanceof StarlarkInt) {
+          // x - y
+          return StarlarkInt.subtract((StarlarkInt) x, (StarlarkInt) y);
         }
         break;
 
       case STAR:
-        if (x instanceof Integer) {
-          int xi = (Integer) x;
-          if (y instanceof Integer) {
+        if (x instanceof StarlarkInt) {
+          StarlarkInt xi = (StarlarkInt) x;
+          if (y instanceof StarlarkInt) {
             // int * int
-            long z = (long) xi * (long) (Integer) y;
-            if ((int) z != z) {
-              throw Starlark.errorf("integer overflow in multiplication");
-            }
-            return (int) z;
+            return StarlarkInt.multiply((StarlarkInt) x, (StarlarkInt) y);
           } else if (y instanceof String) {
             // int * string
             return repeatString((String) y, xi);
@@ -310,21 +277,21 @@
           }
 
         } else if (x instanceof String) {
-          if (y instanceof Integer) {
+          if (y instanceof StarlarkInt) {
             // string * int
-            return repeatString((String) x, (Integer) y);
+            return repeatString((String) x, (StarlarkInt) y);
           }
 
         } else if (x instanceof Tuple) {
-          if (y instanceof Integer) {
+          if (y instanceof StarlarkInt) {
             // tuple * int
-            return ((Tuple<?>) x).repeat((Integer) y);
+            return ((Tuple<?>) x).repeat((StarlarkInt) y);
           }
 
         } else if (x instanceof StarlarkList) {
-          if (y instanceof Integer) {
+          if (y instanceof StarlarkInt) {
             // list * int
-            return ((StarlarkList<?>) x).repeat((Integer) y, mu);
+            return ((StarlarkList<?>) x).repeat((StarlarkInt) y, mu);
           }
         }
         break;
@@ -333,41 +300,17 @@
         throw Starlark.errorf("The `/` operator is not allowed. For integer division, use `//`.");
 
       case SLASH_SLASH:
-        if (x instanceof Integer && y instanceof Integer) {
-          // int // int
-          int xi = (Integer) x;
-          int yi = (Integer) y;
-          if (yi == 0) {
-            throw Starlark.errorf("integer division by zero");
-          }
-          // http://python-history.blogspot.com/2010/08/why-pythons-integer-division-floors.html
-          int quo = xi / yi;
-          int rem = xi % yi;
-          if ((xi < 0) != (yi < 0) && rem != 0) {
-            quo--;
-          }
-          if (xi == Integer.MIN_VALUE && yi == -1) { // HD 2-13
-            throw Starlark.errorf("integer overflow in division");
-          }
-          return quo;
+        if (x instanceof StarlarkInt && y instanceof StarlarkInt) {
+          // x // y
+          return StarlarkInt.floordiv((StarlarkInt) x, (StarlarkInt) y);
         }
         break;
 
       case PERCENT:
-        if (x instanceof Integer) {
-          if (y instanceof Integer) {
-            // int % int
-            int xi = (Integer) x;
-            int yi = (Integer) y;
-            if (yi == 0) {
-              throw Starlark.errorf("integer modulo by zero");
-            }
-            // In Starlark, the sign of the result is the sign of the divisor.
-            int z = xi % yi;
-            if ((xi < 0) != (yi < 0) && z != 0) {
-              z += yi;
-            }
-            return z;
+        if (x instanceof StarlarkInt) {
+          if (y instanceof StarlarkInt) {
+            // x % y
+            return StarlarkInt.mod((StarlarkInt) x, (StarlarkInt) y);
           }
 
         } else if (x instanceof String) {
@@ -453,7 +396,9 @@
     }
   }
 
-  private static String repeatString(String s, int n) {
+  private static String repeatString(String s, StarlarkInt in) throws EvalException {
+    int n = in.toInt("repeat");
+    // TODO(adonovan): reject unreasonably large n.
     return n <= 0 ? "" : Strings.repeat(s, n);
   }
 
@@ -464,24 +409,20 @@
         return !Starlark.truth(x);
 
       case MINUS:
-        if (x instanceof Integer) {
-          int xi = (Integer) x;
-          if (xi == Integer.MIN_VALUE) {
-            throw Starlark.errorf("integer overflow in negation");
-          }
-          return -xi;
+        if (x instanceof StarlarkInt) {
+          return StarlarkInt.uminus((StarlarkInt) x); // -int
         }
         break;
 
       case PLUS:
-        if (x instanceof Integer) {
-          return x;
+        if (x instanceof StarlarkInt) {
+          return x; // +int
         }
         break;
 
       case TILDE:
-        if (x instanceof Integer) {
-          return ~((Integer) x);
+        if (x instanceof StarlarkInt) {
+          return StarlarkInt.bitnot((StarlarkInt) x); // ~int
         }
         break;
 
diff --git a/src/main/java/net/starlark/java/eval/MethodDescriptor.java b/src/main/java/net/starlark/java/eval/MethodDescriptor.java
index f65bcaf..ce75c87 100644
--- a/src/main/java/net/starlark/java/eval/MethodDescriptor.java
+++ b/src/main/java/net/starlark/java/eval/MethodDescriptor.java
@@ -20,6 +20,7 @@
 import java.lang.reflect.Method;
 import java.util.Arrays;
 import javax.annotation.Nullable;
+import net.starlark.java.annot.Param;
 import net.starlark.java.annot.StarlarkMethod;
 
 /**
@@ -85,6 +86,12 @@
     // This happens when the interface is public but the implementation classes
     // have reduced visibility.
     method.setAccessible(true);
+
+    Class<?>[] paramClasses = method.getParameterTypes();
+    Param[] paramAnnots = annotation.parameters();
+    ParamDescriptor[] params = new ParamDescriptor[paramAnnots.length];
+    Arrays.setAll(params, i -> ParamDescriptor.of(paramAnnots[i], paramClasses[i], semantics));
+
     return new MethodDescriptor(
         method,
         annotation,
@@ -92,9 +99,7 @@
         annotation.doc(),
         annotation.documented(),
         annotation.structField(),
-        Arrays.stream(annotation.parameters())
-            .map(param -> ParamDescriptor.of(param, semantics))
-            .toArray(ParamDescriptor[]::new),
+        params,
         !annotation.extraPositionals().name().isEmpty(),
         !annotation.extraKeywords().name().isEmpty(),
         annotation.selfCall(),
diff --git a/src/main/java/net/starlark/java/eval/MethodLibrary.java b/src/main/java/net/starlark/java/eval/MethodLibrary.java
index 3b7f361..65ff604 100644
--- a/src/main/java/net/starlark/java/eval/MethodLibrary.java
+++ b/src/main/java/net/starlark/java/eval/MethodLibrary.java
@@ -19,6 +19,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
+import java.math.BigInteger;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -335,7 +336,7 @@
     return Starlark.truth(x);
   }
 
-  private final ImmutableMap<String, Integer> intPrefixes =
+  private static final ImmutableMap<String, Integer> INT_PREFIXES =
       ImmutableMap.of("0b", 2, "0o", 8, "0x", 16);
 
   @StarlarkMethod(
@@ -381,7 +382,7 @@
         @Param(name = "x", type = Object.class, doc = "The string to convert."),
         @Param(
             name = "base",
-            type = Object.class,
+            type = Integer.class,
             defaultValue = "unbound",
             doc =
                 "The base used to interpret a string value; defaults to 10. Must be between 2 "
@@ -390,33 +391,38 @@
                     + "string.",
             named = true)
       })
-  public Integer convertToInt(Object x, Object base) throws EvalException {
+  public StarlarkInt intForStarlark(Object x, Object base) throws EvalException {
     if (x instanceof String) {
       if (base == Starlark.UNBOUND) {
         base = 10;
       } else if (!(base instanceof Integer)) {
-        throw Starlark.errorf("base must be an integer (got '%s')", Starlark.type(base));
+        throw Starlark.errorf("got %s for base, want int", Starlark.type(base));
       }
-      return fromString((String) x, (Integer) base);
+      try {
+        return StarlarkInt.parse((String) x, (Integer) base);
+      } catch (NumberFormatException ex) {
+        throw Starlark.errorf("%s", ex.getMessage());
+      }
     } else {
       if (base != Starlark.UNBOUND) {
-        throw Starlark.errorf("int() can't convert non-string with explicit base");
+        throw Starlark.errorf("can't convert non-string with explicit base");
       }
       if (x instanceof Boolean) {
-        return ((Boolean) x).booleanValue() ? 1 : 0;
-      } else if (x instanceof Integer) {
-        return (Integer) x;
+        return StarlarkInt.of(((Boolean) x).booleanValue() ? 1 : 0);
+      } else if (x instanceof StarlarkInt) {
+        return (StarlarkInt) x;
       }
-      throw Starlark.errorf("%s is not of type string or int or bool", Starlark.repr(x));
+      throw Starlark.errorf("got %s, want string, int, or bool", Starlark.type(x));
     }
   }
 
-  private int fromString(String string, int base) throws EvalException {
+  // TODO(adonovan): move into StarlarkInt.parse in a follow-up.
+  static StarlarkInt parseInt(String string, int base) throws NumberFormatException {
     String stringForErrors = string;
 
     boolean isNegative = false;
     if (string.isEmpty()) {
-      throw Starlark.errorf("string argument to int() cannot be empty");
+      throw new NumberFormatException("empty string");
     }
     char c = string.charAt(0);
     if (c == '+') {
@@ -435,9 +441,8 @@
         if (string.length() > 1 && string.startsWith("0")) {
           // We don't infer the base when input starts with '0' (due
           // to confusion between octal and decimal).
-          throw Starlark.errorf(
-              "cannot infer base for int() when value begins with a 0: %s",
-              Starlark.repr(stringForErrors));
+          throw new NumberFormatException(
+              "cannot infer base when string begins with a 0: " + Starlark.repr(stringForErrors));
         }
         base = 10;
       }
@@ -445,33 +450,43 @@
       // Strip prefix. Infer base from prefix if unknown (base == 0), or else verify its
       // consistency.
       digits = string.substring(prefix.length());
-      int expectedBase = intPrefixes.get(prefix);
+      int expectedBase = INT_PREFIXES.get(prefix);
       if (base == 0) {
         base = expectedBase;
       } else if (base != expectedBase) {
-        throw Starlark.errorf(
-            "invalid literal for int() with base %d: %s", base, Starlark.repr(stringForErrors));
+        throw new NumberFormatException(
+            String.format(
+                "invalid base-%d literal: %s (%s prefix wants base %d)",
+                base, Starlark.repr(stringForErrors), prefix, expectedBase));
       }
     }
 
     if (base < 2 || base > 36) {
-      throw Starlark.errorf("int() base must be >= 2 and <= 36");
+      throw new NumberFormatException(
+          String.format("invalid base %d (want 2 <= base <= 36)", base));
     }
+    StarlarkInt result;
     try {
-      // Negate by prepending a negative symbol, rather than by using arithmetic on the
-      // result, to handle the edge case of -2^31 correctly.
-      String parseable = isNegative ? "-" + digits : digits;
-      return Integer.parseInt(parseable, base);
-    } catch (NumberFormatException | ArithmeticException e) {
-      throw Starlark.errorf(
-          "invalid literal for int() with base %d: %s", base, Starlark.repr(stringForErrors));
+      result = StarlarkInt.of(Long.parseLong(digits, base));
+    } catch (
+        @SuppressWarnings("UnusedException")
+        NumberFormatException unused1) {
+      try {
+        result = StarlarkInt.of(new BigInteger(digits, base));
+      } catch (
+          @SuppressWarnings("UnusedException")
+          NumberFormatException unused2) {
+        throw new NumberFormatException(
+            String.format("invalid base-%d literal: %s", base, Starlark.repr(stringForErrors)));
+      }
     }
+    return isNegative ? StarlarkInt.uminus(result) : result;
   }
 
   @Nullable
-  private String getIntegerPrefix(String value) {
+  private static String getIntegerPrefix(String value) {
     value = Ascii.toLowerCase(value);
-    for (String prefix : intPrefixes.keySet()) {
+    for (String prefix : INT_PREFIXES.keySet()) {
       if (value.startsWith(prefix)) {
         return prefix;
       }
@@ -529,7 +544,7 @@
       throws EvalException {
     Object[] array = Starlark.toArray(input);
     for (int i = 0; i < array.length; i++) {
-      array[i] = Tuple.pair(i + start, array[i]); // update in place
+      array[i] = Tuple.pair(StarlarkInt.of(i + start), array[i]); // update in place
     }
     return StarlarkList.wrap(thread.mutability(), array);
   }
@@ -580,7 +595,7 @@
             doc = "The increment (default is 1). It may be negative.")
       },
       useStarlarkThread = true)
-  public Sequence<Integer> range(
+  public Sequence<StarlarkInt> range(
       Integer startOrStop, Object stopOrNone, Integer step, StarlarkThread thread)
       throws EvalException {
     int start;
@@ -794,11 +809,8 @@
       name = "int",
       category = "core",
       doc =
-          "A type to represent integers. It can represent any number between -2147483648 and "
-              + "2147483647 (included). "
-              + "Examples of int values:<br>"
-              + "<pre class=\"language-python\">"
-              + "153\n"
+          "The type of integers in Starlark. Starlark integers may be of any magnitude; arithmetic"
+              + " is exact. Examples of int values:<br><pre class=\"language-python\">153\n"
               + "0x2A  # hexadecimal literal\n"
               + "0o54  # octal literal\n"
               + "23 * 2 + 5\n"
diff --git a/src/main/java/net/starlark/java/eval/ParamDescriptor.java b/src/main/java/net/starlark/java/eval/ParamDescriptor.java
index dea16dd..a2d16c2 100644
--- a/src/main/java/net/starlark/java/eval/ParamDescriptor.java
+++ b/src/main/java/net/starlark/java/eval/ParamDescriptor.java
@@ -30,6 +30,7 @@
 final class ParamDescriptor {
 
   private final String name;
+  private final boolean reboxInt; // whether StarlarkInt is converted to Integer
   @Nullable private final Object defaultValue;
   private final boolean noneable;
   private final boolean named;
@@ -46,9 +47,16 @@
       boolean named,
       boolean positional,
       List<Class<?>> allowedClasses,
+      boolean reboxInt,
       @Nullable String disabledByFlag) {
     this.name = name;
-    this.defaultValue = defaultExpr.isEmpty() ? null : evalDefault(name, defaultExpr);
+    this.reboxInt = reboxInt;
+    try {
+      this.defaultValue =
+          defaultExpr.isEmpty() ? null : reboxIntMaybe(evalDefault(name, defaultExpr));
+    } catch (EvalException ex) {
+      throw new IllegalStateException(ex); // bad default
+    }
     this.noneable = noneable;
     this.named = named;
     this.positional = positional;
@@ -57,10 +65,21 @@
   }
 
   /**
+   * Converts (if necessary for this parameter) a StarlarkInt argument to an Integer parameter,
+   * applying a range check.
+   */
+  Object reboxIntMaybe(Object value) throws EvalException {
+    if (reboxInt && value instanceof StarlarkInt) {
+      return ((StarlarkInt) value).toInt(getName()); // may fail
+    }
+    return value;
+  }
+
+  /**
    * Returns a {@link ParamDescriptor} representing the given raw {@link Param} annotation and the
    * given semantics.
    */
-  static ParamDescriptor of(Param param, StarlarkSemantics starlarkSemantics) {
+  static ParamDescriptor of(Param param, Class<?> paramClass, StarlarkSemantics starlarkSemantics) {
     String defaultExpr = param.defaultValue();
     String disabledByFlag = null;
     if (!starlarkSemantics.isFeatureEnabledBasedOnTogglingFlags(
@@ -73,15 +92,28 @@
       Preconditions.checkState(!disabledByFlag.isEmpty());
     }
 
+    // A parameter of type Integer may accept an argument of type StarlarkInt,
+    // in which case BuiltinCallable.fastcall will apply this range check
+    // and convert (rebox) the argument.
+    // We also do this conversion for a parameter type of (say) Object
+    // if the allowedClasses include Integer.
+    boolean reboxInt = paramClass == Integer.class;
+
     // Compute set of allowed classes.
     ParamType[] allowedTypes = param.allowedTypes();
     List<Class<?>> allowedClasses = new ArrayList<>();
     if (allowedTypes.length > 0) {
       for (ParamType pt : allowedTypes) {
         allowedClasses.add(pt.type());
+        if (pt.type() == Integer.class) {
+          reboxInt = true;
+        }
       }
     } else {
       allowedClasses.add(param.type());
+      if (param.type() == Integer.class) {
+        reboxInt = true;
+      }
     }
     if (param.noneable()) {
       // A few annotations redundantly declare NoneType.
@@ -97,6 +129,7 @@
         param.named(),
         param.positional(),
         allowedClasses,
+        reboxInt,
         disabledByFlag);
   }
 
@@ -166,9 +199,9 @@
     } else if (expr.equals("unbound")) {
       return Starlark.UNBOUND;
     } else if (expr.equals("0")) {
-      return 0;
+      return StarlarkInt.of(0);
     } else if (expr.equals("1")) {
-      return 1;
+      return StarlarkInt.of(1);
     } else if (expr.equals("[]")) {
       return StarlarkList.empty();
     } else if (expr.equals("()")) {
diff --git a/src/main/java/net/starlark/java/eval/Printer.java b/src/main/java/net/starlark/java/eval/Printer.java
index 5b4db95..2064d86 100644
--- a/src/main/java/net/starlark/java/eval/Printer.java
+++ b/src/main/java/net/starlark/java/eval/Printer.java
@@ -140,7 +140,7 @@
    * TODO(adonovan): disallow that.
    */
   public Printer repr(Object o) {
-    // atomic values
+    // atomic values (leaves of the object graph)
     if (o == null) {
       // Java null is not a valid Starlark value, but sometimes printers are used on non-Starlark
       // values such as Locations or Nodes.
@@ -150,20 +150,24 @@
       appendQuoted((String) o);
       return this;
 
-    } else if (o instanceof Integer) {
-      this.buffer.append((int) o);
+    } else if (o instanceof StarlarkInt) {
+      ((StarlarkInt) o).repr(this);
       return this;
 
     } else if (o instanceof Boolean) {
       this.append(((boolean) o) ? "True" : "False");
       return this;
 
-    } else if (o instanceof Class) { // (a non-Starlark value)
+    } else if (o instanceof Integer) { // a non-Starlark value
+      this.buffer.append((int) o);
+      return this;
+
+    } else if (o instanceof Class) { // a non-Starlark value
       this.append(Starlark.classType((Class<?>) o));
       return this;
     }
 
-    // compound values
+    // compound values (may form cycles in the object graph)
 
     if (!push(o)) {
       return this.append("..."); // elided cycle
@@ -324,11 +328,11 @@
           Object argument = arguments.get(a++);
           switch (directive) {
             case 'd':
-              if (!(argument instanceof Integer)) {
+              if (!(argument instanceof StarlarkInt || argument instanceof Integer)) {
                 throw new MissingFormatWidthException(
                     "invalid argument " + Starlark.repr(argument) + " for format pattern %d");
               }
-              printer.append(argument.toString());
+              printer.repr(argument);
               continue;
 
             case 'r':
diff --git a/src/main/java/net/starlark/java/eval/RangeList.java b/src/main/java/net/starlark/java/eval/RangeList.java
index e04723c..6e3bbd8 100644
--- a/src/main/java/net/starlark/java/eval/RangeList.java
+++ b/src/main/java/net/starlark/java/eval/RangeList.java
@@ -48,13 +48,14 @@
             + "range(10)[3:0:-1]  # range(3, 0, -1)</pre>"
             + "Ranges are immutable, as in Python 3.")
 @Immutable
-final class RangeList extends AbstractList<Integer> implements Sequence<Integer> {
+final class RangeList extends AbstractList<StarlarkInt> implements Sequence<StarlarkInt> {
 
   private final int start;
   private final int stop;
   private final int step;
   private final int size; // (derived)
 
+  // TODO(adonovan): use StarlarkInt computation, to avoid overflow.
   RangeList(int start, int stop, int step) {
     Preconditions.checkArgument(step != 0);
 
@@ -85,24 +86,29 @@
 
   @Override
   public boolean contains(Object x) {
-    if (!(x instanceof Integer)) {
+    if (!(x instanceof StarlarkInt)) {
       return false;
     }
-    int i = (Integer) x;
-    // constant-time implementation
-    if (step > 0) {
-      return start <= i && i < stop && (i - start) % step == 0;
-    } else {
-      return stop < i && i <= start && (i - start) % step == 0;
+    try {
+      int i = ((StarlarkInt) x).toIntUnchecked();
+
+      // constant-time implementation
+      if (step > 0) {
+        return start <= i && i < stop && (i - start) % step == 0;
+      } else {
+        return stop < i && i <= start && (i - start) % step == 0;
+      }
+    } catch (IllegalArgumentException ex) {
+      return false; // x is not a signed 32-bit int
     }
   }
 
   @Override
-  public Integer get(int index) {
+  public StarlarkInt get(int index) {
     if (index < 0 || index >= size()) {
       throw new ArrayIndexOutOfBoundsException(index + ":" + this);
     }
-    return at(index);
+    return StarlarkInt.of(at(index));
   }
 
   @Override
@@ -136,8 +142,8 @@
   }
 
   @Override
-  public Iterator<Integer> iterator() {
-    return new UnmodifiableIterator<Integer>() {
+  public Iterator<StarlarkInt> iterator() {
+    return new UnmodifiableIterator<StarlarkInt>() {
       int cursor = start;
 
       @Override
@@ -146,19 +152,19 @@
       }
 
       @Override
-      public Integer next() {
+      public StarlarkInt next() {
         if (!hasNext()) {
           throw new NoSuchElementException();
         }
         int current = cursor;
         cursor += step;
-        return current;
+        return StarlarkInt.of(current);
       }
     };
   }
 
   @Override
-  public Sequence<Integer> getSlice(Mutability mu, int start, int stop, int step) {
+  public Sequence<StarlarkInt> getSlice(Mutability mu, int start, int stop, int step) {
     return new RangeList(at(start), at(stop), step * this.step);
   }
 
diff --git a/src/main/java/net/starlark/java/eval/Starlark.java b/src/main/java/net/starlark/java/eval/Starlark.java
index a601bb0..68517d4 100644
--- a/src/main/java/net/starlark/java/eval/Starlark.java
+++ b/src/main/java/net/starlark/java/eval/Starlark.java
@@ -22,6 +22,7 @@
 import java.io.IOException;
 import java.io.OutputStream;
 import java.lang.reflect.Method;
+import java.math.BigInteger;
 import java.time.Duration;
 import java.util.List;
 import java.util.Map;
@@ -93,14 +94,10 @@
   }
 
   /**
-   * Reports whether the argument is a legal Starlark value: a string, boolean, integer, or
-   * StarlarkValue.
+   * Reports whether the argument is a legal Starlark value: a string, boolean, or StarlarkValue.
    */
   public static boolean valid(Object x) {
-    return x instanceof StarlarkValue
-        || x instanceof String
-        || x instanceof Boolean
-        || x instanceof Integer;
+    return x instanceof StarlarkValue || x instanceof String || x instanceof Boolean;
   }
 
   /**
@@ -134,7 +131,7 @@
     // NB: This is used as the basis for accepting objects in Depsets,
     // as well as for accepting objects as keys for Starlark dicts.
 
-    if (x instanceof String || x instanceof Integer || x instanceof Boolean) {
+    if (x instanceof String || x instanceof Boolean) {
       return true;
     } else if (x instanceof StarlarkValue) {
       return ((StarlarkValue) x).isImmutable();
@@ -145,9 +142,9 @@
 
   /**
    * Converts a Java value {@code x} to a Starlark one, if x is not already a valid Starlark value.
-   * A Java List or Map is converted to a Starlark list or dict, respectively, and null becomes
-   * {@link #NONE}. Any other non-Starlark value causes the function to throw
-   * IllegalArgumentException.
+   * An Integer, Long, or BigInteger is converted to a Starlark int, a Java List or Map is converted
+   * to a Starlark list or dict, respectively, and null becomes {@link #NONE}. Any other
+   * non-Starlark value causes the function to throw IllegalArgumentException.
    *
    * <p>This function is applied to the results of StarlarkMethod-annotated Java methods.
    */
@@ -156,14 +153,20 @@
       return NONE;
     } else if (valid(x)) {
       return x;
+    } else if (x instanceof Number) {
+      if (x instanceof Integer) {
+        return StarlarkInt.of((Integer) x);
+      } else if (x instanceof Long) {
+        return StarlarkInt.of((Long) x);
+      } else if (x instanceof BigInteger) {
+        return StarlarkInt.of((BigInteger) x);
+      }
     } else if (x instanceof List) {
       return StarlarkList.copyOf(mutability, (List<?>) x);
     } else if (x instanceof Map) {
       return Dict.copyOf(mutability, (Map<?, ?>) x);
-    } else {
-      throw new IllegalArgumentException(
-          "cannot expose internal type to Starlark: " + x.getClass());
     }
+    throw new IllegalArgumentException("cannot expose internal type to Starlark: " + x.getClass());
   }
 
   /**
@@ -177,8 +180,6 @@
       return ((StarlarkValue) x).truth();
     } else if (x instanceof String) {
       return !((String) x).isEmpty();
-    } else if (x instanceof Integer) {
-      return (Integer) x != 0;
     } else {
       throw new IllegalArgumentException("invalid Starlark value: " + x.getClass());
     }
@@ -263,14 +264,17 @@
    * for reporting error messages involving arbitrary Java classes, for example at the interface
    * between Starlark and Java.
    */
-  // TODO(adonovan): reconsider allowing any classes other than String, Integer, Boolean, and
-  // subclasses of StarlarkValue, with a special exception for Object.class meaning "any Starlark
-  // value" (not: any Java object). Ditto for Depset.ElementType.
   public static String classType(Class<?> c) {
     // Check for "direct hits" first to avoid needing to scan for annotations.
     if (c.equals(String.class)) {
       return "string";
+    } else if (StarlarkInt.class.isAssignableFrom(c)) {
+      return "int";
     } else if (c.equals(Integer.class)) {
+      // Integer is not a legal Starlark value, but it is used for parameter types
+      // in built-in functions; StarlarkBuiltin.fastcall does a range check
+      // and reboxing. Use of this type means "signed 32-bit int value",
+      // but that's a lot for an error message.
       return "int";
     } else if (c.equals(Boolean.class)) {
       return "bool";
@@ -291,6 +295,8 @@
       return "function";
     } else if (c.equals(RangeList.class)) {
       return "range";
+    } else if (c.equals(UnboundMarker.class)) {
+      return "unbound";
     }
 
     StarlarkBuiltin module = StarlarkInterfaceUtils.getStarlarkBuiltin(c);
@@ -436,11 +442,20 @@
     }
   }
 
-  static int toInt(Object x, String name) throws EvalException {
-    if (x instanceof Integer) {
-      return (Integer) x;
+  /**
+   * Returns the signed 32-bit value of a Starlark int. Throws an exception including {@code what}
+   * if x is not a Starlark int or its value is not exactly representable as a Java int.
+   *
+   * @throws IllegalArgumentException if x is an Integer, which is not a Starlark value.
+   */
+  public static int toInt(Object x, String what) throws EvalException {
+    if (x instanceof StarlarkInt) {
+      return ((StarlarkInt) x).toInt(what);
     }
-    throw errorf("got %s for %s, want int", type(x), name);
+    if (x instanceof Integer) {
+      throw new IllegalArgumentException("Integer is not a legal Starlark value");
+    }
+    throw errorf("got %s for %s, want int", type(x), what);
   }
 
   /**
diff --git a/src/main/java/net/starlark/java/eval/StarlarkInt.java b/src/main/java/net/starlark/java/eval/StarlarkInt.java
new file mode 100644
index 0000000..97c159d
--- /dev/null
+++ b/src/main/java/net/starlark/java/eval/StarlarkInt.java
@@ -0,0 +1,497 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package net.starlark.java.eval;
+
+import java.math.BigInteger;
+
+/** The Starlark int data type. */
+// No StarlarkBuiltin(name="int") annotation because it would cause docgen
+// to complain that two things are called int (the type and the function).
+// TODO(adonovan): fix docgen.
+public abstract class StarlarkInt implements StarlarkValue, Comparable<StarlarkInt> {
+
+  // A cache of small integers >= LEAST_SMALLINT.
+  private static final int LEAST_SMALLINT = -128;
+  private static final Int32[] smallints = new Int32[100_000];
+
+  static final StarlarkInt ZERO = StarlarkInt.of(0);
+
+  /** Returns the Starlark int value that represents x. */
+  public static StarlarkInt of(int x) {
+    int index = x - LEAST_SMALLINT; // (may overflow)
+    if (0 <= index && index < smallints.length) {
+      Int32 xi = smallints[index];
+      if (xi == null) {
+        xi = new Int32(x);
+        smallints[index] = xi;
+      }
+      return xi;
+    }
+    return new Int32(x);
+  }
+
+  /** Returns the Starlark int value that represents x. */
+  public static StarlarkInt of(long x) {
+    if ((long) (int) x == x) {
+      return StarlarkInt.of((int) x);
+    }
+    return new Int64(x);
+  }
+
+  /** Returns the Starlark int value that represents x. */
+  public static StarlarkInt of(BigInteger x) {
+    if (x.bitLength() < 64) {
+      return StarlarkInt.of(x.longValue());
+    }
+    return new Big(x);
+  }
+
+  /**
+   * Returns the int denoted by a literal string in the specified base, as if by the Starlark
+   * expression {@code int(str, base)}.
+   */
+  public static StarlarkInt parse(String str, int base) throws NumberFormatException {
+    return MethodLibrary.parseInt(str, base);
+  }
+
+  // Subclass for values exactly representable in a Java int.
+  private static final class Int32 extends StarlarkInt {
+    final int v;
+
+    Int32(int v) {
+      this.v = v;
+    }
+
+    @Override
+    public int toInt(String what) {
+      return v;
+    }
+
+    @Override
+    public long toLong(String what) {
+      return (long) v;
+    }
+
+    @Override
+    public BigInteger toBigInteger() {
+      return BigInteger.valueOf(v);
+    }
+
+    @Override
+    public Number toNumber() {
+      return v;
+    }
+
+    @Override
+    public int signum() {
+      return Integer.signum(v);
+    }
+
+    @Override
+    public int hashCode() {
+      return 0x316c5239 * Integer.hashCode(v) ^ 0x67c4a7d5;
+    }
+
+    @Override
+    public boolean equals(Object that) {
+      return that instanceof Int32 && this.v == ((Int32) that).v;
+    }
+  }
+
+  // Subclass for values exactly representable in a Java long.
+  private static final class Int64 extends StarlarkInt {
+    final long v;
+
+    Int64(long v) {
+      this.v = v;
+    }
+
+    @Override
+    public long toLong(String what) {
+      return v;
+    }
+
+    @Override
+    public BigInteger toBigInteger() {
+      return BigInteger.valueOf(v);
+    }
+
+    @Override
+    public Number toNumber() {
+      return v;
+    }
+
+    @Override
+    public int signum() {
+      return Long.signum(v);
+    }
+
+    @Override
+    public int hashCode() {
+      return 0x67c4a7d5 * Long.hashCode(v) ^ 0xee914a1b;
+    }
+
+    @Override
+    public boolean equals(Object that) {
+      return that instanceof Int64 && this.v == ((Int64) that).v;
+    }
+  }
+
+  // Subclass for values not exactly representable in a long.
+  private static final class Big extends StarlarkInt {
+    final BigInteger v;
+
+    Big(BigInteger v) {
+      this.v = v;
+    }
+
+    @Override
+    public BigInteger toBigInteger() {
+      return v;
+    }
+
+    @Override
+    public Number toNumber() {
+      return v;
+    }
+
+    @Override
+    public int signum() {
+      return v.signum();
+    }
+
+    @Override
+    public int hashCode() {
+      return 0xee914a1b * v.hashCode() ^ 0x6406918f;
+    }
+
+    @Override
+    public boolean equals(Object that) {
+      return that instanceof Big && this.v.equals(((Big) that).v);
+    }
+  }
+
+  /** Returns the value of this StarlarkInt as a Number (Integer, Long, or BigInteger). */
+  public abstract Number toNumber();
+
+  /** Returns the signum of this StarlarkInt (-1, 0, or +1). */
+  public abstract int signum();
+
+  /** Returns this StarlarkInt as a string of decimal digits. */
+  @Override
+  public String toString() {
+    // TODO(adonovan): opt: avoid Number allocation
+    return toNumber().toString();
+  }
+
+  @Override
+  public void repr(Printer printer) {
+    // TODO(adonovan): opt: avoid Number and String allocations.
+    printer.append(toString());
+  }
+
+  /** Returns the signed int32 value of this StarlarkInt, or fails if not exactly representable. */
+  public int toInt(String what) throws EvalException {
+    throw Starlark.errorf("got %s for %s, want value in signed 32-bit range", this, what);
+  }
+
+  /** Returns the signed int64 value of this StarlarkInt, or fails if not exactly representable. */
+  public long toLong(String what) throws EvalException {
+    throw Starlark.errorf("got %s for %s, want value in the signed 64-bit range", this, what);
+  }
+
+  /** Returns the BigInteger value of this StarlarkInt. */
+  public abstract BigInteger toBigInteger();
+
+  /**
+   * Returns the the value of this StarlarkInt as a Java signed 32-bit int.
+   *
+   * @throws IllegalArgumentException if this int is not in that value range.
+   */
+  public final int toIntUnchecked() throws IllegalArgumentException {
+    if (this instanceof Int32) {
+      return ((Int32) this).v;
+    }
+    // Use a constant exception to avoid allocation.
+    // This operator is provided for fast access and case discrimination.
+    // Use toInt(String) for user-visible errors.
+    throw NOT_INT32;
+  }
+
+  private static final IllegalArgumentException NOT_INT32 =
+      new IllegalArgumentException("not a signed 32-bit value");
+
+  /** Returns the result of truncating this value into the signed 32-bit range. */
+  public int truncateToInt() {
+    if (this instanceof Int32) {
+      return ((Int32) this).v;
+    } else if (this instanceof Int64) {
+      return (int) ((Int64) this).v;
+    } else {
+      return toBigInteger().intValue();
+    }
+  }
+
+  @Override
+  public boolean isImmutable() {
+    return true;
+  }
+
+  @Override
+  public boolean truth() {
+    return this != ZERO;
+  }
+
+  @Override
+  public int compareTo(StarlarkInt x) {
+    return compare(this, x);
+  }
+
+  // binary operators
+
+  // In the common case, both operands are int32, so the operations
+  // can be done using longs with minimal fuss around overflows.
+  // All other combinations are promoted to BigInteger.
+  // TODO(adonovan): use long x long operations even if one or both
+  // operands are Int64; promote to Big x Big only upon overflow.
+  // (See the revision history for the necessary overflow checks.)
+
+  /** Returns a value whose signum is equal to x - y. */
+  public static int compare(StarlarkInt x, StarlarkInt y) {
+    if (x instanceof Int32 && y instanceof Int32) {
+      return Integer.compare(((Int32) x).v, ((Int32) y).v);
+    }
+    return x.toBigInteger().compareTo(y.toBigInteger());
+  }
+
+  /** Returns x + y. */
+  public static StarlarkInt add(StarlarkInt x, StarlarkInt y) {
+    if (x instanceof Int32 && y instanceof Int32) {
+      long xl = ((Int32) x).v;
+      long yl = ((Int32) y).v;
+      return StarlarkInt.of(xl + yl);
+    }
+
+    BigInteger xbig = x.toBigInteger();
+    BigInteger ybig = y.toBigInteger();
+    BigInteger zbig = xbig.add(ybig);
+    return StarlarkInt.of(zbig);
+  }
+
+  /** Returns x - y. */
+  public static StarlarkInt subtract(StarlarkInt x, StarlarkInt y) {
+    if (x instanceof Int32 && y instanceof Int32) {
+      long xl = ((Int32) x).v;
+      long yl = ((Int32) y).v;
+      return StarlarkInt.of(xl - yl);
+    }
+
+    BigInteger xbig = x.toBigInteger();
+    BigInteger ybig = y.toBigInteger();
+    BigInteger zbig = xbig.subtract(ybig);
+    return StarlarkInt.of(zbig);
+  }
+
+  /** Returns x * y. */
+  public static StarlarkInt multiply(StarlarkInt x, StarlarkInt y) {
+    if (x instanceof Int32 && y instanceof Int32) {
+      long xl = ((Int32) x).v;
+      long yl = ((Int32) y).v;
+      return StarlarkInt.of(xl * yl);
+    }
+
+    BigInteger xbig = x.toBigInteger();
+    BigInteger ybig = y.toBigInteger();
+    BigInteger zbig = xbig.multiply(ybig);
+    return StarlarkInt.of(zbig);
+  }
+
+  /** Returns x / y (real division). */
+  public static StarlarkInt divide(StarlarkInt x, StarlarkInt y) throws EvalException {
+    if (y == ZERO) {
+      throw Starlark.errorf("real division by zero");
+    }
+    if (x instanceof Int32 && y instanceof Int32) {
+      long xl = ((Int32) x).v;
+      long yl = ((Int32) y).v;
+      return StarlarkInt.of(xl / yl);
+    }
+
+    BigInteger xbig = x.toBigInteger();
+    BigInteger ybig = y.toBigInteger();
+    BigInteger zbig = xbig.divide(ybig);
+    return StarlarkInt.of(zbig);
+  }
+
+  /** Returns x // y (floor of integer division). */
+  public static StarlarkInt floordiv(StarlarkInt x, StarlarkInt y) throws EvalException {
+    if (y == ZERO) {
+      throw Starlark.errorf("integer division by zero");
+    }
+    if (x instanceof Int32 && y instanceof Int32) {
+      long xl = ((Int32) x).v;
+      long yl = ((Int32) y).v;
+      // http://python-history.blogspot.com/2010/08/why-pythons-integer-division-floors.html
+      long quo = xl / yl;
+      long rem = xl % yl;
+      if ((xl < 0) != (yl < 0) && rem != 0) {
+        quo--;
+      }
+      return StarlarkInt.of(quo);
+    }
+
+    BigInteger xbig = x.toBigInteger();
+    BigInteger ybig = y.toBigInteger();
+    BigInteger[] quorem = xbig.divideAndRemainder(ybig);
+    if ((xbig.signum() < 0) != (ybig.signum() < 0) && quorem[1].signum() != 0) {
+      quorem[0] = quorem[0].subtract(BigInteger.ONE);
+    }
+    return StarlarkInt.of(quorem[0]);
+  }
+
+  /** Returns x % y. */
+  public static StarlarkInt mod(StarlarkInt x, StarlarkInt y) throws EvalException {
+    if (y == ZERO) {
+      throw Starlark.errorf("integer modulo by zero");
+    }
+    if (x instanceof Int32 && y instanceof Int32) {
+      long xl = ((Int32) x).v;
+      long yl = ((Int32) y).v;
+      // In Starlark, the sign of the result is the sign of the divisor.
+      long z = xl % yl;
+      if ((xl < 0) != (yl < 0) && z != 0) {
+        z += yl;
+      }
+      return StarlarkInt.of(z);
+    }
+
+    BigInteger xbig = x.toBigInteger();
+    BigInteger ybig = y.toBigInteger();
+    BigInteger zbig = xbig.remainder(ybig);
+    if ((x.signum() < 0) != (y.signum() < 0) && zbig.signum() != 0) {
+      zbig = zbig.add(ybig);
+    }
+    return StarlarkInt.of(zbig);
+  }
+
+  /** Returns x >> y. */
+  public static StarlarkInt shiftRight(StarlarkInt x, StarlarkInt y) throws EvalException {
+    int yi = y.toInt("shift count");
+    if (yi < 0) {
+      throw Starlark.errorf("negative shift count: %d", yi);
+    }
+    if (x instanceof Int32) {
+      long xl = ((Int32) x).v;
+      if (yi >= Integer.SIZE) {
+        return xl < 0 ? StarlarkInt.of(-1) : ZERO;
+      }
+      return StarlarkInt.of(xl >> yi);
+    }
+
+    BigInteger xbig = x.toBigInteger();
+    BigInteger zbig = xbig.shiftRight(yi);
+    return StarlarkInt.of(zbig);
+  }
+
+  /** Returns x << y. */
+  public static StarlarkInt shiftLeft(StarlarkInt x, StarlarkInt y) throws EvalException {
+    int yi = y.toInt("shift count");
+    if (yi < 0) {
+      throw Starlark.errorf("negative shift count: %d", yi);
+    } else if (yi >= 512) {
+      throw Starlark.errorf("shift count too large: %d", yi);
+    }
+    if (x instanceof Int32) {
+      long xl = ((Int32) x).v;
+      long z = xl << yi; // only uses low 6 bits of yi
+      if ((z >> yi) == xl && yi < 64) {
+        return StarlarkInt.of(z);
+      }
+      /* overflow */
+    }
+
+    BigInteger xbig = x.toBigInteger();
+    BigInteger zbig = xbig.shiftLeft(yi);
+    return StarlarkInt.of(zbig);
+  }
+
+  /** Returns x ^ y. */
+  public static StarlarkInt xor(StarlarkInt x, StarlarkInt y) {
+    if (x instanceof Int32 && y instanceof Int32) {
+      long xl = ((Int32) x).v;
+      long yl = ((Int32) y).v;
+      return StarlarkInt.of(xl ^ yl);
+    }
+
+    BigInteger xbig = x.toBigInteger();
+    BigInteger ybig = y.toBigInteger();
+    BigInteger zbig = xbig.xor(ybig);
+    return StarlarkInt.of(zbig);
+  }
+
+  /** Returns x | y. */
+  public static StarlarkInt or(StarlarkInt x, StarlarkInt y) {
+    if (x instanceof Int32 && y instanceof Int32) {
+      long xl = ((Int32) x).v;
+      long yl = ((Int32) y).v;
+      return StarlarkInt.of(xl | yl);
+    }
+
+    BigInteger xbig = x.toBigInteger();
+    BigInteger ybig = y.toBigInteger();
+    BigInteger zbig = xbig.or(ybig);
+    return StarlarkInt.of(zbig);
+  }
+
+  /** Returns x & y. */
+  public static StarlarkInt and(StarlarkInt x, StarlarkInt y) {
+    if (x instanceof Int32 && y instanceof Int32) {
+      long xl = ((Int32) x).v;
+      long yl = ((Int32) y).v;
+      return StarlarkInt.of(xl & yl);
+    }
+
+    BigInteger xbig = x.toBigInteger();
+    BigInteger ybig = y.toBigInteger();
+    BigInteger zbig = xbig.and(ybig);
+    return StarlarkInt.of(zbig);
+  }
+
+  /** Returns ~x. */
+  public static StarlarkInt bitnot(StarlarkInt x) {
+    if (x instanceof Int32) {
+      long xl = ((Int32) x).v;
+      return StarlarkInt.of(~xl);
+    }
+
+    BigInteger xbig = x.toBigInteger();
+    BigInteger ybig = MINUS1BIG.subtract(xbig);
+    return StarlarkInt.of(ybig);
+  }
+
+  /** Returns -x. */
+  public static StarlarkInt uminus(StarlarkInt x) {
+    if (x instanceof Int32) {
+      long xl = ((Int32) x).v;
+      return StarlarkInt.of(-xl);
+    }
+
+    BigInteger xbig = x.toBigInteger();
+    BigInteger ybig = xbig.negate();
+    return StarlarkInt.of(ybig);
+  }
+
+  private static final BigInteger MINUS1BIG = BigInteger.ONE.negate();
+}
diff --git a/src/main/java/net/starlark/java/eval/StarlarkList.java b/src/main/java/net/starlark/java/eval/StarlarkList.java
index 23b07b6..e658dae 100644
--- a/src/main/java/net/starlark/java/eval/StarlarkList.java
+++ b/src/main/java/net/starlark/java/eval/StarlarkList.java
@@ -215,14 +215,15 @@
   }
 
   /** Returns a new StarlarkList containing n consecutive repeats of this tuple. */
-  public StarlarkList<E> repeat(int n, Mutability mutability) {
-    if (n <= 0) {
+  public StarlarkList<E> repeat(StarlarkInt n, Mutability mutability) throws EvalException {
+    if (n.signum() <= 0) {
       return wrap(mutability, EMPTY_ARRAY);
     }
 
     // TODO(adonovan): reject unreasonably large n.
-    Object[] res = new Object[n * size];
-    for (int i = 0; i < n; i++) {
+    int ni = n.toInt("repeat");
+    Object[] res = new Object[ni * size];
+    for (int i = 0; i < ni; i++) {
       System.arraycopy(elems, 0, res, i * size, size);
     }
     return wrap(mutability, res);
diff --git a/src/main/java/net/starlark/java/eval/StringModule.java b/src/main/java/net/starlark/java/eval/StringModule.java
index b9fb864..09cdf31 100644
--- a/src/main/java/net/starlark/java/eval/StringModule.java
+++ b/src/main/java/net/starlark/java/eval/StringModule.java
@@ -545,13 +545,13 @@
         @Param(name = "sub", type = String.class, doc = "The substring to find."),
         @Param(
             name = "start",
-            type = Integer.class,
+            type = StarlarkInt.class,
             noneable = true,
             defaultValue = "0",
             doc = "Restrict to search from this position."),
         @Param(
             name = "end",
-            type = Integer.class,
+            type = StarlarkInt.class,
             noneable = true,
             defaultValue = "None",
             doc = "optional position before which to restrict to search.")
@@ -571,13 +571,13 @@
         @Param(name = "sub", type = String.class, doc = "The substring to find."),
         @Param(
             name = "start",
-            type = Integer.class,
+            type = StarlarkInt.class,
             noneable = true,
             defaultValue = "0",
             doc = "Restrict to search from this position."),
         @Param(
             name = "end",
-            type = Integer.class,
+            type = StarlarkInt.class,
             noneable = true,
             defaultValue = "None",
             doc = "optional position before which to restrict to search.")
@@ -597,13 +597,13 @@
         @Param(name = "sub", type = String.class, doc = "The substring to find."),
         @Param(
             name = "start",
-            type = Integer.class,
+            type = StarlarkInt.class,
             noneable = true,
             defaultValue = "0",
             doc = "Restrict to search from this position."),
         @Param(
             name = "end",
-            type = Integer.class,
+            type = StarlarkInt.class,
             noneable = true,
             defaultValue = "None",
             doc = "optional position before which to restrict to search.")
@@ -627,13 +627,13 @@
         @Param(name = "sub", type = String.class, doc = "The substring to find."),
         @Param(
             name = "start",
-            type = Integer.class,
+            type = StarlarkInt.class,
             noneable = true,
             defaultValue = "0",
             doc = "Restrict to search from this position."),
         @Param(
             name = "end",
-            type = Integer.class,
+            type = StarlarkInt.class,
             noneable = true,
             defaultValue = "None",
             doc = "optional position before which to restrict to search.")
@@ -820,13 +820,13 @@
         @Param(name = "sub", type = String.class, doc = "The substring to count."),
         @Param(
             name = "start",
-            type = Integer.class,
+            type = StarlarkInt.class,
             noneable = true,
             defaultValue = "0",
             doc = "Restrict to search from this position."),
         @Param(
             name = "end",
-            type = Integer.class,
+            type = StarlarkInt.class,
             noneable = true,
             defaultValue = "None",
             doc = "optional position before which to restrict to search.")
@@ -881,13 +881,13 @@
             doc = "The suffix (or tuple of alternative suffixes) to match."),
         @Param(
             name = "start",
-            type = Integer.class,
+            type = StarlarkInt.class,
             noneable = true,
             defaultValue = "0",
             doc = "Test beginning at this position."),
         @Param(
             name = "end",
-            type = Integer.class,
+            type = StarlarkInt.class,
             noneable = true,
             defaultValue = "None",
             doc = "optional position at which to stop comparing.")
@@ -968,13 +968,13 @@
             doc = "The prefix (or tuple of alternative prefixes) to match."),
         @Param(
             name = "start",
-            type = Integer.class,
+            type = StarlarkInt.class,
             noneable = true,
             defaultValue = "0",
             doc = "Test beginning at this position."),
         @Param(
             name = "end",
-            type = Integer.class,
+            type = StarlarkInt.class,
             noneable = true,
             defaultValue = "None",
             doc = "Stop comparing at this position.")
diff --git a/src/main/java/net/starlark/java/eval/Tuple.java b/src/main/java/net/starlark/java/eval/Tuple.java
index f7da45c..83b7f3b 100644
--- a/src/main/java/net/starlark/java/eval/Tuple.java
+++ b/src/main/java/net/starlark/java/eval/Tuple.java
@@ -228,13 +228,15 @@
   }
 
   /** Returns a Tuple containing n consecutive repeats of this tuple. */
-  Tuple<E> repeat(int n) {
-    if (n <= 0 || isEmpty()) {
+  Tuple<E> repeat(StarlarkInt n) throws EvalException {
+    if (n.signum() <= 0 || isEmpty()) {
       return empty();
     }
+
     // TODO(adonovan): reject unreasonably large n.
-    Object[] res = new Object[n * elems.length];
-    for (int i = 0; i < n; i++) {
+    int ni = n.toInt("repeat");
+    Object[] res = new Object[ni * elems.length];
+    for (int i = 0; i < ni; i++) {
       System.arraycopy(elems, 0, res, i * elems.length, elems.length);
     }
     return wrap(res);
diff --git a/src/main/java/net/starlark/java/syntax/IntegerLiteral.java b/src/main/java/net/starlark/java/syntax/IntegerLiteral.java
index de123f2..313e06c 100644
--- a/src/main/java/net/starlark/java/syntax/IntegerLiteral.java
+++ b/src/main/java/net/starlark/java/syntax/IntegerLiteral.java
@@ -14,6 +14,7 @@
 package net.starlark.java.syntax;
 
 /** Syntax node for an integer literal. */
+// TODO(adonovan): support literals of arbitrary size.
 public final class IntegerLiteral extends Expression {
   private final String raw;
   private final int tokenOffset;
diff --git a/src/test/java/com/google/devtools/build/docgen/RuleDocumentationAttributeTest.java b/src/test/java/com/google/devtools/build/docgen/RuleDocumentationAttributeTest.java
index c5a5667..76bb485 100644
--- a/src/test/java/com/google/devtools/build/docgen/RuleDocumentationAttributeTest.java
+++ b/src/test/java/com/google/devtools/build/docgen/RuleDocumentationAttributeTest.java
@@ -23,6 +23,7 @@
 import com.google.devtools.build.lib.packages.BuildType;
 import com.google.devtools.build.lib.packages.Type;
 import com.google.devtools.build.lib.rules.cpp.CppFileTypes;
+import net.starlark.java.eval.StarlarkInt;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -116,7 +117,7 @@
 
   @Test
   public void testSynopsisForIntegerAttribute() {
-    final int defaultValue = 384;
+    StarlarkInt defaultValue = StarlarkInt.of(384);
     Attribute attribute = Attribute.attr("bar_limit", Type.INTEGER)
         .value(defaultValue).build();
     RuleDocumentationAttribute attributeDoc = RuleDocumentationAttribute.create(
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/StarlarkAttrTransitionProviderTest.java b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkAttrTransitionProviderTest.java
index fce6873..70aa7e0 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/StarlarkAttrTransitionProviderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/StarlarkAttrTransitionProviderTest.java
@@ -46,6 +46,7 @@
 import java.util.stream.Collectors;
 import net.starlark.java.eval.Dict;
 import net.starlark.java.eval.Starlark;
+import net.starlark.java.eval.StarlarkInt;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -1050,7 +1051,7 @@
                 .getOptions()
                 .getStarlarkOptions()
                 .get(Label.parseAbsoluteUnchecked("//test/starlark:the-answer")))
-        .isEqualTo(42);
+        .isEqualTo(StarlarkInt.of(42));
   }
 
   @Test
@@ -1084,7 +1085,7 @@
                 .getOptions()
                 .getStarlarkOptions()
                 .get(Label.parseAbsoluteUnchecked("//test/starlark:the-answer")))
-        .isEqualTo(42);
+        .isEqualTo(StarlarkInt.of(42));
   }
 
   private CoreOptions getCoreOptions(ConfiguredTarget target) {
diff --git a/src/test/java/com/google/devtools/build/lib/collect/nestedset/DepsetTest.java b/src/test/java/com/google/devtools/build/lib/collect/nestedset/DepsetTest.java
index 5ef6a39..9eacd9e 100644
--- a/src/test/java/com/google/devtools/build/lib/collect/nestedset/DepsetTest.java
+++ b/src/test/java/com/google/devtools/build/lib/collect/nestedset/DepsetTest.java
@@ -22,6 +22,7 @@
 import net.starlark.java.eval.Dict;
 import net.starlark.java.eval.Sequence;
 import net.starlark.java.eval.StarlarkCallable;
+import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.eval.StarlarkIterable;
 import net.starlark.java.eval.StarlarkList;
 import net.starlark.java.eval.StarlarkValue;
@@ -68,7 +69,10 @@
             Tuple.of("1", "3", "5"), Tuple.of("1", "2"), Tuple.of("3", "4"), Tuple.of("5", "6"));
     assertThat(get("s_eight").getSet(Tuple.class).toList())
         .containsExactly(
-            Tuple.of(1, 3), Tuple.of("1", "2"), Tuple.of("3", "4"), Tuple.of("5", "6"));
+            Tuple.of(StarlarkInt.of(1), StarlarkInt.of(3)),
+            Tuple.of("1", "2"),
+            Tuple.of("3", "4"),
+            Tuple.of("5", "6"));
   }
 
   @Test
@@ -76,12 +80,14 @@
     ev.exec("s = depset(['a', 'b'])");
     assertThat(get("s").getSet(String.class).toList()).containsExactly("a", "b").inOrder();
     assertThat(get("s").getSet(Object.class).toList()).containsExactly("a", "b").inOrder();
-    assertThrows(Depset.TypeException.class, () -> get("s").getSet(Integer.class));
+    assertThrows(Depset.TypeException.class, () -> get("s").getSet(StarlarkInt.class));
 
     // getSet argument must be a legal Starlark value class, or Object,
     // but not some superclass that doesn't implement StarlarkValue.
-    Depset ints = Depset.legacyOf(Order.STABLE_ORDER, Tuple.of(1, 2, 3));
-    assertThat(ints.getSet(Integer.class).toString()).isEqualTo("[1, 2, 3]");
+    Depset ints =
+        Depset.legacyOf(
+            Order.STABLE_ORDER, Tuple.of(StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3)));
+    assertThat(ints.getSet(StarlarkInt.class).toString()).isEqualTo("[1, 2, 3]");
     IllegalArgumentException ex =
         assertThrows(IllegalArgumentException.class, () -> ints.getSet(Number.class));
     assertThat(ex.getMessage()).contains("Number is not a subclass of StarlarkValue");
@@ -92,7 +98,7 @@
     ev.exec("s = depset(direct = ['a', 'b'])");
     assertThat(get("s").getSet(String.class).toList()).containsExactly("a", "b").inOrder();
     assertThat(get("s").getSet(Object.class).toList()).containsExactly("a", "b").inOrder();
-    assertThrows(Depset.TypeException.class, () -> get("s").getSet(Integer.class));
+    assertThrows(Depset.TypeException.class, () -> get("s").getSet(StarlarkInt.class));
   }
 
   @Test
@@ -100,7 +106,7 @@
     ev.exec("s = depset(items = ['a', 'b'])");
     assertThat(get("s").getSet(String.class).toList()).containsExactly("a", "b").inOrder();
     assertThat(get("s").getSet(Object.class).toList()).containsExactly("a", "b").inOrder();
-    assertThrows(Depset.TypeException.class, () -> get("s").getSet(Integer.class));
+    assertThrows(Depset.TypeException.class, () -> get("s").getSet(StarlarkInt.class));
   }
 
   @Test
@@ -109,7 +115,7 @@
     assertThat(get("s").toList(String.class)).containsExactly("a", "b").inOrder();
     assertThat(get("s").toList(Object.class)).containsExactly("a", "b").inOrder();
     assertThat(get("s").toList()).containsExactly("a", "b").inOrder();
-    assertThrows(Depset.TypeException.class, () -> get("s").toList(Integer.class));
+    assertThrows(Depset.TypeException.class, () -> get("s").toList(StarlarkInt.class));
   }
 
   @Test
@@ -118,7 +124,7 @@
     assertThat(get("s").toList(String.class)).containsExactly("a", "b").inOrder();
     assertThat(get("s").toList(Object.class)).containsExactly("a", "b").inOrder();
     assertThat(get("s").toList()).containsExactly("a", "b").inOrder();
-    assertThrows(Depset.TypeException.class, () -> get("s").toList(Integer.class));
+    assertThrows(Depset.TypeException.class, () -> get("s").toList(StarlarkInt.class));
   }
 
   @Test
@@ -127,7 +133,7 @@
     assertThat(get("s").toList(String.class)).containsExactly("a", "b").inOrder();
     assertThat(get("s").toList(Object.class)).containsExactly("a", "b").inOrder();
     assertThat(get("s").toList()).containsExactly("a", "b").inOrder();
-    assertThrows(Depset.TypeException.class, () -> get("s").toList(Integer.class));
+    assertThrows(Depset.TypeException.class, () -> get("s").toList(StarlarkInt.class));
   }
 
   @Test
@@ -322,10 +328,11 @@
 
   @Test
   public void testToListForStarlark() throws Exception {
-    ev.exec("s = depset([3, 4, 5], transitive = [depset([2, 4, 6])])", "x = s.to_list()");
-    Object value = ev.lookup("x");
-    assertThat(value).isInstanceOf(StarlarkList.class);
-    assertThat((Iterable<?>) value).containsExactly(2, 4, 6, 3, 5).inOrder();
+    ev.exec(
+        "s = depset([3, 4, 5], transitive = [depset([2, 4, 6])])",
+        "x = s.to_list()",
+        "y = [2, 4, 6, 3, 5]");
+    assertThat(ev.lookup("x")).isEqualTo(ev.lookup("y"));
   }
 
   @Test
@@ -469,7 +476,7 @@
   public void testElementTypeOf() {
     // legal values
     assertThat(ElementType.of(String.class).toString()).isEqualTo("string");
-    assertThat(ElementType.of(Integer.class).toString()).isEqualTo("int");
+    assertThat(ElementType.of(StarlarkInt.class).toString()).isEqualTo("int");
     assertThat(ElementType.of(Boolean.class).toString()).isEqualTo("bool");
 
     // concrete non-values
diff --git a/src/test/java/com/google/devtools/build/lib/packages/AttributeTest.java b/src/test/java/com/google/devtools/build/lib/packages/AttributeTest.java
index 998a9fe..75b4d8e 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/AttributeTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/AttributeTest.java
@@ -42,6 +42,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import net.starlark.java.eval.StarlarkInt;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -60,9 +61,9 @@
 
   @Test
   public void testBasics() throws Exception {
-    Attribute attr = attr("foo", Type.INTEGER).mandatory().value(3).build();
+    Attribute attr = attr("foo", Type.INTEGER).mandatory().value(StarlarkInt.of(3)).build();
     assertThat(attr.getName()).isEqualTo("foo");
-    assertThat(attr.getDefaultValue(null)).isEqualTo(3);
+    assertThat(attr.getDefaultValue(null)).isEqualTo(StarlarkInt.of(3));
     assertThat(attr.getType()).isEqualTo(Type.INTEGER);
     assertThat(attr.isMandatory()).isTrue();
     assertThat(attr.isDocumented()).isTrue();
@@ -75,7 +76,7 @@
     NullPointerException e =
         assertThrows(
             NullPointerException.class,
-            () -> attr("foo", Type.INTEGER).nonEmpty().value(3).build());
+            () -> attr("foo", Type.INTEGER).nonEmpty().value(StarlarkInt.of(3)).build());
     assertThat(e).hasMessageThat().isEqualTo("attribute 'foo' must be a list");
   }
 
@@ -92,7 +93,7 @@
     IllegalStateException e =
         assertThrows(
             IllegalStateException.class,
-            () -> attr("foo", Type.INTEGER).singleArtifact().value(3).build());
+            () -> attr("foo", Type.INTEGER).singleArtifact().value(StarlarkInt.of(3)).build());
     assertThat(e).hasMessageThat().isEqualTo("attribute 'foo' must be a label-valued type");
   }
 
@@ -119,10 +120,8 @@
    */
   @Test
   public void testConvenienceFactoriesDefaultValues() throws Exception {
-    assertDefaultValue(0,
-                       attr("x", INTEGER).build());
-    assertDefaultValue(42,
-                       attr("x", INTEGER).value(42).build());
+    assertDefaultValue(StarlarkInt.of(0), attr("x", INTEGER).build());
+    assertDefaultValue(StarlarkInt.of(42), attr("x", INTEGER).value(StarlarkInt.of(42)).build());
 
     assertDefaultValue("",
                        attr("x", STRING).build());
@@ -158,8 +157,7 @@
   public void testConvenienceFactoriesTypes() throws Exception {
     assertType(INTEGER,
                attr("x", INTEGER).build());
-    assertType(INTEGER,
-               attr("x", INTEGER).value(42).build());
+    assertType(INTEGER, attr("x", INTEGER).value(StarlarkInt.of(42)).build());
 
     assertType(STRING,
                attr("x", STRING).build());
diff --git a/src/test/java/com/google/devtools/build/lib/packages/RuleClassBuilderTest.java b/src/test/java/com/google/devtools/build/lib/packages/RuleClassBuilderTest.java
index ad4c470..73e303c 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/RuleClassBuilderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/RuleClassBuilderTest.java
@@ -30,6 +30,7 @@
 import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassNamePredicate;
 import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
 import com.google.devtools.build.lib.packages.util.PackageLoadingTestCase;
+import net.starlark.java.eval.StarlarkInt;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -82,7 +83,7 @@
             .add(attr("size", STRING).value("medium"))
             .add(attr("timeout", STRING))
             .add(attr("flaky", BOOLEAN).value(false))
-            .add(attr("shard_count", INTEGER).value(-1))
+            .add(attr("shard_count", INTEGER).value(StarlarkInt.of(-1)))
             .add(attr("local", BOOLEAN))
             .build();
     assertThat(ruleClassA.hasBinaryOutput()).isTrue();
diff --git a/src/test/java/com/google/devtools/build/lib/packages/RuleClassTest.java b/src/test/java/com/google/devtools/build/lib/packages/RuleClassTest.java
index 1e7ee19..4512457 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/RuleClassTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/RuleClassTest.java
@@ -68,6 +68,7 @@
 import java.util.Set;
 import javax.annotation.Nullable;
 import net.starlark.java.eval.StarlarkFunction;
+import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.eval.StarlarkSemantics;
 import net.starlark.java.eval.StarlarkThread;
 import net.starlark.java.syntax.Location;
@@ -129,7 +130,7 @@
             .value(Label.parseAbsolute("//default:label", ImmutableMap.of()))
             .build(),
         attr("my-labellist-attr", LABEL_LIST).mandatory().legacyAllowAnyFileType().build(),
-        attr("my-integer-attr", INTEGER).value(42).build(),
+        attr("my-integer-attr", INTEGER).value(StarlarkInt.of(42)).build(),
         attr("my-string-attr2", STRING).mandatory().value((String) null).build(),
         attr("my-stringlist-attr", STRING_LIST).build(),
         attr("my-sorted-stringlist-attr", STRING_LIST).orderIndependent().build());
@@ -194,7 +195,7 @@
     assertThat(ruleClassA.getAttribute(1).getDefaultValue(null))
         .isEqualTo(Label.parseAbsolute("//default:label", ImmutableMap.of()));
     assertThat(ruleClassA.getAttribute(2).getDefaultValue(null)).isEqualTo(Collections.emptyList());
-    assertThat(ruleClassA.getAttribute(3).getDefaultValue(null)).isEqualTo(42);
+    assertThat(ruleClassA.getAttribute(3).getDefaultValue(null)).isEqualTo(StarlarkInt.of(42));
     // default explicitly specified
     assertThat(ruleClassA.getAttribute(4).getDefaultValue(null)).isNull();
     assertThat(ruleClassA.getAttribute(5).getDefaultValue(null)).isEqualTo(Collections.emptyList());
@@ -386,7 +387,7 @@
     AttributeMap attributes = RawAttributeMapper.of(rule);
     assertThat(attributes.get("my-label-attr", BuildType.LABEL).toString())
         .isEqualTo("//default:label");
-    assertThat(attributes.get("my-integer-attr", Type.INTEGER).intValue()).isEqualTo(42);
+    assertThat(attributes.get("my-integer-attr", Type.INTEGER).toIntUnchecked()).isEqualTo(42);
     // missing attribute -> default chosen based on type
     assertThat(attributes.get("my-string-attr", Type.STRING)).isEmpty();
     assertThat(attributes.get("my-labellist-attr", BuildType.LABEL_LIST)).isEmpty();
diff --git a/src/test/java/com/google/devtools/build/lib/packages/SelectTest.java b/src/test/java/com/google/devtools/build/lib/packages/SelectTest.java
index 418b75a..3bcd5ad 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/SelectTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/SelectTest.java
@@ -22,6 +22,7 @@
 import net.starlark.java.eval.Module;
 import net.starlark.java.eval.Mutability;
 import net.starlark.java.eval.Starlark;
+import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.eval.StarlarkSemantics;
 import net.starlark.java.eval.StarlarkThread;
 import net.starlark.java.syntax.FileOptions;
@@ -55,7 +56,7 @@
   public void testSelect() throws Exception {
     SelectorList result = (SelectorList) eval("select({'a': 1})");
     assertThat(((SelectorValue) Iterables.getOnlyElement(result.getElements())).getDictionary())
-        .containsExactly("a", 1);
+        .containsExactly("a", StarlarkInt.of(1));
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/packages/StarlarkInfoTest.java b/src/test/java/com/google/devtools/build/lib/packages/StarlarkInfoTest.java
index d3595bd..cb34612 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/StarlarkInfoTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/StarlarkInfoTest.java
@@ -26,6 +26,7 @@
 import java.util.Random;
 import javax.annotation.Nullable;
 import net.starlark.java.eval.EvalException;
+import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.eval.StarlarkValue;
 import net.starlark.java.syntax.Location;
 import net.starlark.java.syntax.TokenKind;
@@ -46,14 +47,14 @@
   @Test
   public void instancesOfUnexportedProvidersAreMutable() throws Exception {
     StarlarkProvider provider = makeProvider();
-    StarlarkInfo info = makeInfoWithF1F2Values(provider, 5, null);
+    StarlarkInfo info = makeInfoWithF1F2Values(provider, StarlarkInt.of(5), null);
     assertThat(info.isImmutable()).isFalse();
   }
 
   @Test
   public void instancesOfExportedProvidersMayBeImmutable() throws Exception {
     StarlarkProvider provider = makeExportedProvider();
-    StarlarkInfo info = makeInfoWithF1F2Values(provider, 5, null);
+    StarlarkInfo info = makeInfoWithF1F2Values(provider, StarlarkInt.of(5), null);
     assertThat(info.isImmutable()).isTrue();
   }
 
@@ -61,7 +62,7 @@
   public void mutableIfContentsAreMutable() throws Exception {
     StarlarkProvider provider = makeExportedProvider();
     StarlarkValue v = new StarlarkValue() {};
-    StarlarkInfo info = makeInfoWithF1F2Values(provider, 5, v);
+    StarlarkInfo info = makeInfoWithF1F2Values(provider, StarlarkInt.of(5), v);
     assertThat(info.isImmutable()).isFalse();
   }
 
@@ -70,25 +71,25 @@
     StarlarkProvider provider1 = makeProvider();
     StarlarkProvider provider2 = makeProvider();
     // equal providers and fields
-    assertThat(makeInfoWithF1F2Values(provider1, 4, 5))
-        .isEqualTo(makeInfoWithF1F2Values(provider1, 4, 5));
+    assertThat(makeInfoWithF1F2Values(provider1, StarlarkInt.of(4), StarlarkInt.of(5)))
+        .isEqualTo(makeInfoWithF1F2Values(provider1, StarlarkInt.of(4), StarlarkInt.of(5)));
     // different providers => unequal
-    assertThat(makeInfoWithF1F2Values(provider1, 4, 5))
-        .isNotEqualTo(makeInfoWithF1F2Values(provider2, 4, 5));
+    assertThat(makeInfoWithF1F2Values(provider1, StarlarkInt.of(4), StarlarkInt.of(5)))
+        .isNotEqualTo(makeInfoWithF1F2Values(provider2, StarlarkInt.of(4), StarlarkInt.of(5)));
     // different fields => unequal
-    assertThat(makeInfoWithF1F2Values(provider1, 4, 5))
-        .isNotEqualTo(makeInfoWithF1F2Values(provider1, 4, 6));
+    assertThat(makeInfoWithF1F2Values(provider1, StarlarkInt.of(4), StarlarkInt.of(5)))
+        .isNotEqualTo(makeInfoWithF1F2Values(provider1, StarlarkInt.of(4), StarlarkInt.of(6)));
     // different sets of fields => unequal
-    assertThat(makeInfoWithF1F2Values(provider1, 4, 5))
-        .isNotEqualTo(makeInfoWithF1F2Values(provider1, 4, null));
+    assertThat(makeInfoWithF1F2Values(provider1, StarlarkInt.of(4), StarlarkInt.of(5)))
+        .isNotEqualTo(makeInfoWithF1F2Values(provider1, StarlarkInt.of(4), null));
   }
 
   @Test
   public void concatWithDifferentProvidersFails() throws Exception {
     StarlarkProvider provider1 = makeProvider();
     StarlarkProvider provider2 = makeProvider();
-    StarlarkInfo info1 = makeInfoWithF1F2Values(provider1, 4, 5);
-    StarlarkInfo info2 = makeInfoWithF1F2Values(provider2, 4, 5);
+    StarlarkInfo info1 = makeInfoWithF1F2Values(provider1, StarlarkInt.of(4), StarlarkInt.of(5));
+    StarlarkInfo info2 = makeInfoWithF1F2Values(provider2, StarlarkInt.of(4), StarlarkInt.of(5));
     EvalException expected =
         assertThrows(EvalException.class, () -> info1.binaryOp(TokenKind.PLUS, info2, true));
     assertThat(expected).hasMessageThat()
@@ -98,8 +99,8 @@
   @Test
   public void concatWithOverlappingFieldsFails() throws Exception {
     StarlarkProvider provider1 = makeProvider();
-    StarlarkInfo info1 = makeInfoWithF1F2Values(provider1, 4, 5);
-    StarlarkInfo info2 = makeInfoWithF1F2Values(provider1, 4, null);
+    StarlarkInfo info1 = makeInfoWithF1F2Values(provider1, StarlarkInt.of(4), StarlarkInt.of(5));
+    StarlarkInfo info2 = makeInfoWithF1F2Values(provider1, StarlarkInt.of(4), null);
     EvalException expected =
         assertThrows(EvalException.class, () -> info1.binaryOp(TokenKind.PLUS, info2, true));
     assertThat(expected)
@@ -110,23 +111,23 @@
   @Test
   public void concatWithSameFields() throws Exception {
     StarlarkProvider provider = makeProvider();
-    StarlarkInfo info1 = makeInfoWithF1F2Values(provider, 4, null);
-    StarlarkInfo info2 = makeInfoWithF1F2Values(provider, null, 5);
+    StarlarkInfo info1 = makeInfoWithF1F2Values(provider, StarlarkInt.of(4), null);
+    StarlarkInfo info2 = makeInfoWithF1F2Values(provider, null, StarlarkInt.of(5));
     StarlarkInfo result = info1.binaryOp(TokenKind.PLUS, info2, true);
     assertThat(result.getFieldNames()).containsExactly("f1", "f2");
-    assertThat(result.getValue("f1")).isEqualTo(4);
-    assertThat(result.getValue("f2")).isEqualTo(5);
+    assertThat(result.getValue("f1")).isEqualTo(StarlarkInt.of(4));
+    assertThat(result.getValue("f2")).isEqualTo(StarlarkInt.of(5));
   }
 
   @Test
   public void concatWithDifferentFields() throws Exception {
     StarlarkProvider provider = makeProvider();
-    StarlarkInfo info1 = makeInfoWithF1F2Values(provider, 4, null);
-    StarlarkInfo info2 = makeInfoWithF1F2Values(provider, null, 5);
+    StarlarkInfo info1 = makeInfoWithF1F2Values(provider, StarlarkInt.of(4), null);
+    StarlarkInfo info2 = makeInfoWithF1F2Values(provider, null, StarlarkInt.of(5));
     StarlarkInfo result = info1.binaryOp(TokenKind.PLUS, info2, true);
     assertThat(result.getFieldNames()).containsExactly("f1", "f2");
-    assertThat(result.getValue("f1")).isEqualTo(4);
-    assertThat(result.getValue("f2")).isEqualTo(5);
+    assertThat(result.getValue("f1")).isEqualTo(StarlarkInt.of(4));
+    assertThat(result.getValue("f2")).isEqualTo(StarlarkInt.of(5));
   }
 
   /** Creates an unexported schemaless provider type with builtin location. */
diff --git a/src/test/java/com/google/devtools/build/lib/packages/TypeTest.java b/src/test/java/com/google/devtools/build/lib/packages/TypeTest.java
index 7265c0d..776e183 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/TypeTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/TypeTest.java
@@ -28,6 +28,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.eval.StarlarkList;
 import net.starlark.java.eval.Tuple;
 import org.junit.Before;
@@ -50,9 +51,27 @@
 
   @Test
   public void testInteger() throws Exception {
-    Object x = 3;
+    Object x = StarlarkInt.of(3);
     assertThat(Type.INTEGER.convert(x, null)).isEqualTo(x);
     assertThat(collectLabels(Type.INTEGER, x)).isEmpty();
+
+    // INTEGER rule attributes must be in signed 32-bit value range.
+    // (If we ever relax this, we'll need to audit every place that
+    // converts an attribute to an int using toIntUnchecked, since
+    // that operation might then fail, and extend the Package
+    // serialization protocol to support bigint.)
+    StarlarkInt big = StarlarkInt.of(111111111);
+    Type.ConversionException e =
+        assertThrows(
+            Type.ConversionException.class,
+            () -> Type.INTEGER.convert(StarlarkInt.multiply(big, big), "param"));
+    assertThat(e)
+        .hasMessageThat()
+        .contains("for param, got 12345678987654321, want value in signed 32-bit range");
+
+    // Ensure that the range of INTEGER.concat is int32.
+    assertThat(Type.INTEGER.concat(Arrays.asList(StarlarkInt.of(0x7fffffff), StarlarkInt.of(1))))
+        .isEqualTo(StarlarkInt.of(-0x80000000));
   }
 
   @Test
@@ -96,8 +115,8 @@
   public void testBoolean() throws Exception {
     Object myTrue = true;
     Object myFalse = false;
-    assertThat(Type.BOOLEAN.convert(1, null)).isEqualTo(Boolean.TRUE);
-    assertThat(Type.BOOLEAN.convert(0, null)).isEqualTo(Boolean.FALSE);
+    assertThat(Type.BOOLEAN.convert(StarlarkInt.of(1), null)).isEqualTo(Boolean.TRUE);
+    assertThat(Type.BOOLEAN.convert(StarlarkInt.of(0), null)).isEqualTo(Boolean.FALSE);
     assertThat(Type.BOOLEAN.convert(true, null)).isTrue();
     assertThat(Type.BOOLEAN.convert(myTrue, null)).isTrue();
     assertThat(Type.BOOLEAN.convert(false, null)).isFalse();
@@ -114,17 +133,21 @@
         .hasMessageThat()
         .isEqualTo("expected value of type 'int', but got \"unexpected\" (string)");
     // Integers other than [0, 1] should fail.
-    e = assertThrows(Type.ConversionException.class, () -> Type.BOOLEAN.convert(2, null));
+    e =
+        assertThrows(
+            Type.ConversionException.class, () -> Type.BOOLEAN.convert(StarlarkInt.of(2), null));
     assertThat(e).hasMessageThat().isEqualTo("boolean is not one of [0, 1]");
-    e = assertThrows(Type.ConversionException.class, () -> Type.BOOLEAN.convert(-1, null));
+    e =
+        assertThrows(
+            Type.ConversionException.class, () -> Type.BOOLEAN.convert(StarlarkInt.of(-1), null));
     assertThat(e).hasMessageThat().isEqualTo("boolean is not one of [0, 1]");
   }
 
   @Test
   public void testTriState() throws Exception {
-    assertThat(BuildType.TRISTATE.convert(1, null)).isEqualTo(TriState.YES);
-    assertThat(BuildType.TRISTATE.convert(0, null)).isEqualTo(TriState.NO);
-    assertThat(BuildType.TRISTATE.convert(-1, null)).isEqualTo(TriState.AUTO);
+    assertThat(BuildType.TRISTATE.convert(StarlarkInt.of(1), null)).isEqualTo(TriState.YES);
+    assertThat(BuildType.TRISTATE.convert(StarlarkInt.of(0), null)).isEqualTo(TriState.NO);
+    assertThat(BuildType.TRISTATE.convert(StarlarkInt.of(-1), null)).isEqualTo(TriState.AUTO);
     assertThat(BuildType.TRISTATE.convert(TriState.YES, null)).isEqualTo(TriState.YES);
     assertThat(BuildType.TRISTATE.convert(TriState.NO, null)).isEqualTo(TriState.NO);
     assertThat(BuildType.TRISTATE.convert(TriState.AUTO, null)).isEqualTo(TriState.AUTO);
@@ -137,9 +160,10 @@
 
   @Test
   public void testTriStateDoesNotAcceptArbitraryIntegers() throws Exception {
-    List<Integer> listOfCases = Lists.newArrayList(2, 3, -5, -2, 20);
-    for (Object entry : listOfCases) {
-      assertThrows(Type.ConversionException.class, () -> BuildType.TRISTATE.convert(entry, null));
+    for (Integer i : Lists.newArrayList(2, 3, -5, -2, 20)) {
+      assertThrows(
+          Type.ConversionException.class,
+          () -> BuildType.TRISTATE.convert(StarlarkInt.of(i), null));
     }
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/profiler/memory/AllocationTrackerTest.java b/src/test/java/com/google/devtools/build/lib/profiler/memory/AllocationTrackerTest.java
index 7991337..acee66b 100644
--- a/src/test/java/com/google/devtools/build/lib/profiler/memory/AllocationTrackerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/profiler/memory/AllocationTrackerTest.java
@@ -37,6 +37,7 @@
 import net.starlark.java.eval.Mutability;
 import net.starlark.java.eval.Starlark;
 import net.starlark.java.eval.StarlarkCallable;
+import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.eval.StarlarkSemantics;
 import net.starlark.java.eval.StarlarkThread;
 import net.starlark.java.syntax.FileOptions;
@@ -64,8 +65,8 @@
   private class SamplerValue implements HasBinary {
     @Override
     public Object binaryOp(TokenKind op, Object that, boolean thisLeft) throws EvalException {
-      if (op == TokenKind.PLUS && thisLeft && that instanceof Integer) {
-        int size = (Integer) that;
+      if (op == TokenKind.PLUS && thisLeft && that instanceof StarlarkInt) {
+        int size = ((StarlarkInt) that).toIntUnchecked(); // test values are small
         Object obj = new Object();
         live.add(obj); // ensure that obj outlives the test assertions
         tracker.sampleAllocation(1, "", obj, size);
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 7e50d69..9a3a906 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
@@ -33,6 +33,7 @@
 import java.util.LinkedHashMap;
 import java.util.Map;
 import net.starlark.java.eval.EvalException;
+import net.starlark.java.eval.StarlarkInt;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.function.ThrowingRunnable;
@@ -120,7 +121,8 @@
 
   @Test
   public void getTransitiveSources_WrongType() {
-    StructImpl info = makeStruct(ImmutableMap.of(PyStructUtils.TRANSITIVE_SOURCES, 123));
+    StructImpl info =
+        makeStruct(ImmutableMap.of(PyStructUtils.TRANSITIVE_SOURCES, StarlarkInt.of(123)));
     assertThrowsEvalExceptionContaining(
         () -> PyStructUtils.getTransitiveSources(info),
         "for transitive_sources, got int, want a depset of File");
@@ -151,7 +153,8 @@
 
   @Test
   public void getUsesSharedLibraries_WrongType() {
-    StructImpl info = makeStruct(ImmutableMap.of(PyStructUtils.USES_SHARED_LIBRARIES, 123));
+    StructImpl info =
+        makeStruct(ImmutableMap.of(PyStructUtils.USES_SHARED_LIBRARIES, StarlarkInt.of(123)));
     assertHasWrongTypeMessage(
         () -> PyStructUtils.getUsesSharedLibraries(info), "uses_shared_libraries", "bool");
   }
@@ -172,7 +175,7 @@
 
   @Test
   public void getImports_WrongType() {
-    StructImpl info = makeStruct(ImmutableMap.of(PyStructUtils.IMPORTS, 123));
+    StructImpl info = makeStruct(ImmutableMap.of(PyStructUtils.IMPORTS, StarlarkInt.of(123)));
     assertThrowsEvalExceptionContaining(
         () -> PyStructUtils.getImports(info), "for imports, got int, want a depset of string");
   }
@@ -190,7 +193,8 @@
 
   @Test
   public void getHasPy2OnlySources_WrongType() {
-    StructImpl info = makeStruct(ImmutableMap.of(PyStructUtils.HAS_PY2_ONLY_SOURCES, 123));
+    StructImpl info =
+        makeStruct(ImmutableMap.of(PyStructUtils.HAS_PY2_ONLY_SOURCES, StarlarkInt.of(123)));
     assertHasWrongTypeMessage(
         () -> PyStructUtils.getHasPy2OnlySources(info), "has_py2_only_sources", "bool");
   }
@@ -208,7 +212,8 @@
 
   @Test
   public void getHasPy3OnlySources_WrongType() {
-    StructImpl info = makeStruct(ImmutableMap.of(PyStructUtils.HAS_PY3_ONLY_SOURCES, 123));
+    StructImpl info =
+        makeStruct(ImmutableMap.of(PyStructUtils.HAS_PY3_ONLY_SOURCES, StarlarkInt.of(123)));
     assertHasWrongTypeMessage(
         () -> PyStructUtils.getHasPy3OnlySources(info), "has_py3_only_sources", "bool");
   }
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/BzlLoadFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/BzlLoadFunctionTest.java
index c505229..e274f4f 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/BzlLoadFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/BzlLoadFunctionTest.java
@@ -47,6 +47,7 @@
 import java.io.InputStream;
 import java.util.UUID;
 import javax.annotation.Nullable;
+import net.starlark.java.eval.StarlarkInt;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -402,7 +403,8 @@
         SkyframeExecutorTestUtils.evaluate(
             getSkyframeExecutor(), skyKey, /*keepGoing=*/ false, reporter);
 
-    assertThat(result.get(skyKey).getModule().getGlobals()).containsEntry("a_symbol", 5);
+    assertThat(result.get(skyKey).getModule().getGlobals())
+        .containsEntry("a_symbol", StarlarkInt.of(5));
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java
index 3a1b30c..01c5faa 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java
@@ -85,6 +85,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import javax.annotation.Nullable;
 import net.starlark.java.eval.Module;
+import net.starlark.java.eval.StarlarkInt;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -1254,7 +1255,7 @@
     Module cViaB = bLoads.get(":c.bzl");
     assertThat(cViaB).isSameInstanceAs(cViaA);
 
-    assertThat(cViaA.getGlobal("c")).isEqualTo(0);
+    assertThat(cViaA.getGlobal("c")).isEqualTo(StarlarkInt.of(0));
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkDefinedAspectsTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkDefinedAspectsTest.java
index fb74540..fb8086a 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkDefinedAspectsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkDefinedAspectsTest.java
@@ -52,6 +52,7 @@
 import com.google.devtools.build.lib.skyframe.AspectValueKey.AspectKey;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import net.starlark.java.eval.Sequence;
+import net.starlark.java.eval.StarlarkInt;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -2460,7 +2461,7 @@
         new StarlarkProvider.Key(
             Label.parseAbsolute("//test:aspect.bzl", ImmutableMap.of()), "PCollector");
     StructImpl collector = (StructImpl) configuredAspect.get(pCollector);
-    assertThat(collector.getValue("attr_value")).isEqualTo(30);
+    assertThat(collector.getValue("attr_value")).isEqualTo(StarlarkInt.of(30));
   }
 
   @Test
@@ -2515,7 +2516,7 @@
         new StarlarkProvider.Key(
             Label.parseAbsolute("//test:aspect.bzl", ImmutableMap.of()), "PCollector");
     StructImpl collector = (StructImpl) configuredAspect.get(pCollector);
-    assertThat(collector.getValue("attr_value")).isEqualTo(30);
+    assertThat(collector.getValue("attr_value")).isEqualTo(StarlarkInt.of(30));
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkIntegrationTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkIntegrationTest.java
index 8d17f32..ac0aa23 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkIntegrationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkIntegrationTest.java
@@ -59,6 +59,7 @@
 import net.starlark.java.eval.NoneType;
 import net.starlark.java.eval.Sequence;
 import net.starlark.java.eval.Starlark;
+import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.eval.StarlarkList;
 import org.junit.Before;
 import org.junit.Test;
@@ -1412,7 +1413,7 @@
     StructImpl declaredProvider = (StructImpl) configuredTarget.get(key);
     assertThat(declaredProvider).isNotNull();
     assertThat(declaredProvider.getProvider().getKey()).isEqualTo(key);
-    assertThat(declaredProvider.getValue("x")).isEqualTo(1);
+    assertThat(declaredProvider.getValue("x")).isEqualTo(StarlarkInt.of(1));
   }
 
   @Test
@@ -1437,7 +1438,7 @@
     StructImpl declaredProvider = (StructImpl) configuredTarget.get(key);
     assertThat(declaredProvider).isNotNull();
     assertThat(declaredProvider.getProvider().getKey()).isEqualTo(key);
-    assertThat(declaredProvider.getValue("x")).isEqualTo(1);
+    assertThat(declaredProvider.getValue("x")).isEqualTo(StarlarkInt.of(1));
   }
 
   @Test
@@ -1463,7 +1464,7 @@
     StructImpl declaredProvider = (StructImpl) configuredTarget.get(key);
     assertThat(declaredProvider).isNotNull();
     assertThat(declaredProvider.getProvider().getKey()).isEqualTo(key);
-    assertThat(declaredProvider.getValue("x")).isEqualTo(1);
+    assertThat(declaredProvider.getValue("x")).isEqualTo(StarlarkInt.of(1));
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkOptionsParsingTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkOptionsParsingTest.java
index ea16805..ab72af3 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkOptionsParsingTest.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkOptionsParsingTest.java
@@ -23,6 +23,7 @@
 import com.google.devtools.build.lib.util.Pair;
 import com.google.devtools.common.options.OptionsParsingException;
 import com.google.devtools.common.options.OptionsParsingResult;
+import net.starlark.java.eval.StarlarkInt;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -39,7 +40,8 @@
     OptionsParsingResult result = parseStarlarkOptions("--//test:my_int_setting=666");
 
     assertThat(result.getStarlarkOptions()).hasSize(1);
-    assertThat(result.getStarlarkOptions().get("//test:my_int_setting")).isEqualTo(666);
+    assertThat(result.getStarlarkOptions().get("//test:my_int_setting"))
+        .isEqualTo(StarlarkInt.of(666));
     assertThat(result.getResidue()).isEmpty();
   }
 
@@ -51,7 +53,8 @@
     OptionsParsingResult result = parseStarlarkOptions("--//test:my_int_setting 666");
 
     assertThat(result.getStarlarkOptions()).hasSize(1);
-    assertThat(result.getStarlarkOptions().get("//test:my_int_setting")).isEqualTo(666);
+    assertThat(result.getStarlarkOptions().get("//test:my_int_setting"))
+        .isEqualTo(StarlarkInt.of(666));
     assertThat(result.getResidue()).isEmpty();
   }
 
@@ -66,7 +69,7 @@
 
     assertThat(result.getStarlarkOptions()).hasSize(1);
     assertThat(result.getStarlarkOptions().get("@starlark_options_test//test:my_int_setting"))
-        .isEqualTo(666);
+        .isEqualTo(StarlarkInt.of(666));
     assertThat(result.getResidue()).isEmpty();
   }
 
@@ -206,7 +209,8 @@
         parseStarlarkOptions("--//test:my_int_setting=4 --//test:my_int_setting=7");
 
     assertThat(result.getStarlarkOptions()).hasSize(1);
-    assertThat(result.getStarlarkOptions().get("//test:my_int_setting")).isEqualTo(7);
+    assertThat(result.getStarlarkOptions().get("//test:my_int_setting"))
+        .isEqualTo(StarlarkInt.of(7));
     assertThat(result.getResidue()).isEmpty();
   }
 
@@ -232,8 +236,10 @@
 
     assertThat(result.getResidue()).isEmpty();
     assertThat(result.getStarlarkOptions()).hasSize(2);
-    assertThat(result.getStarlarkOptions().get("//test:my_int_setting")).isEqualTo(0);
-    assertThat(result.getStarlarkOptions().get("//test:my_other_int_setting")).isEqualTo(0);
+    assertThat(result.getStarlarkOptions().get("//test:my_int_setting"))
+        .isEqualTo(StarlarkInt.of(0));
+    assertThat(result.getStarlarkOptions().get("//test:my_other_int_setting"))
+        .isEqualTo(StarlarkInt.of(0));
   }
 
   // test --non_build_setting
@@ -318,7 +324,8 @@
 
     OptionsParsingResult result = parseStarlarkOptions("--//test:my_int_setting=15");
 
-    assertThat(result.getStarlarkOptions().get("//test:my_int_setting")).isEqualTo(15);
+    assertThat(result.getStarlarkOptions().get("//test:my_int_setting"))
+        .isEqualTo(StarlarkInt.of(15));
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java
index 7a87200..45c3d2b 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java
@@ -61,6 +61,7 @@
 import net.starlark.java.eval.Module;
 import net.starlark.java.eval.Mutability;
 import net.starlark.java.eval.Starlark;
+import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.eval.StarlarkList;
 import net.starlark.java.eval.Tuple;
 import net.starlark.java.syntax.ParserInput;
@@ -639,8 +640,8 @@
   public void testAttrIntValues() throws Exception {
     Attribute attr = buildAttribute("a1", "attr.int(values = [1, 2])");
     PredicateWithMessage<Object> predicate = attr.getAllowedValues();
-    assertThat(predicate.apply(2)).isTrue();
-    assertThat(predicate.apply(3)).isFalse();
+    assertThat(predicate.apply(StarlarkInt.of(2))).isTrue();
+    assertThat(predicate.apply(StarlarkInt.of(3))).isFalse();
   }
 
   @Test
@@ -893,7 +894,50 @@
         "r1 = rule(impl, attrs = {'a1': attr.int(default = 40+2)})");
     RuleClass c = ((StarlarkRuleFunction) ev.lookup("r1")).getRuleClass();
     Attribute a = c.getAttributeByName("a1");
-    assertThat(a.getDefaultValueUnchecked()).isEqualTo(42);
+    assertThat(a.getDefaultValueUnchecked()).isEqualTo(StarlarkInt.of(42));
+  }
+
+  @Test
+  public void testIntDefaultValueMustBeInt32() throws Exception {
+    // This is a test of the loading phase. Move somewhere more appropriate.
+    ev.checkEvalErrorContains(
+        "for parameter 'default' of attribute '', got 4294967296, want value in signed 32-bit"
+            + " range",
+        "attr.int(default = 0x10000 * 0x10000)");
+    ev.checkEvalErrorContains(
+        "for element 0 of parameter 'default' of attribute '', got 4294967296, want value in"
+            + " signed 32-bit range",
+        "attr.int_list(default = [0x10000 * 0x10000])");
+  }
+
+  @Test
+  public void testIntAttributeValueMustBeInt32() throws Exception {
+    // This is a test of the loading phase. Move somewhere more appropriate.
+    scratch.file(
+        "p/inc.bzl", //
+        "def _impl(ctx): pass",
+        "r = rule(_impl, attrs = dict(i=attr.int()))");
+    scratch.file(
+        "p/BUILD", //
+        "load('inc.bzl', 'r')",
+        "r(name = 'p', i = 0x10000 * 0x10000)");
+    AssertionError expected = assertThrows(AssertionError.class, () -> createRuleContext("//p"));
+    assertThat(expected)
+        .hasMessageThat()
+        .contains(
+            "for attribute 'i' in 'r' rule, got 4294967296, want value in signed 32-bit range");
+  }
+
+  @Test
+  public void testIntegerConcatTruncates() throws Exception {
+    // The Type.INTEGER.concat operator, as used to resolve select(int)+select(int)
+    // after rule construction, has a range of int32.
+    scratch.file(
+        "p/BUILD", //
+        "s = select({'//conditions:default': -0x7fffffff})", // -0x7fffffff + -0x7fffffff = 2
+        "cc_test(name='c', shard_count = s+s)");
+    StarlarkRuleContext context = createRuleContext("//p:c");
+    assertThat(context.getAttr().getValue("shard_count")).isEqualTo(StarlarkInt.of(2));
   }
 
   @Test
@@ -1126,8 +1170,8 @@
     // TODO(fwe): cannot be handled by current testing suite
     ev.exec("x = struct(a = 1, b = 2)");
     ClassObject x = (ClassObject) ev.lookup("x");
-    assertThat(x.getValue("a")).isEqualTo(1);
-    assertThat(x.getValue("b")).isEqualTo(2);
+    assertThat(x.getValue("a")).isEqualTo(StarlarkInt.of(1));
+    assertThat(x.getValue("b")).isEqualTo(StarlarkInt.of(2));
   }
 
   @Test
@@ -1162,8 +1206,8 @@
   @Test
   public void testStructAccessingFieldsFromStarlark() throws Exception {
     ev.exec("x = struct(a = 1, b = 2)", "x1 = x.a", "x2 = x.b");
-    assertThat(ev.lookup("x1")).isEqualTo(1);
-    assertThat(ev.lookup("x2")).isEqualTo(2);
+    assertThat(ev.lookup("x1")).isEqualTo(StarlarkInt.of(1));
+    assertThat(ev.lookup("x2")).isEqualTo(StarlarkInt.of(2));
   }
 
   @Test
@@ -1189,7 +1233,7 @@
   @Test
   public void testStructAccessingFunctionFieldWithArgs() throws Exception {
     ev.exec("def f(x): return x+5", "x = struct(a = f, b = 2)", "x1 = x.a(1)");
-    assertThat(ev.lookup("x1")).isEqualTo(6);
+    assertThat(ev.lookup("x1")).isEqualTo(StarlarkInt.of(6));
   }
 
   @Test
@@ -1217,10 +1261,10 @@
         "y = struct(c = 1, d = 2)",
         "z = x + y\n");
     StructImpl z = (StructImpl) ev.lookup("z");
-    assertThat(z.getValue("a")).isEqualTo(1);
-    assertThat(z.getValue("b")).isEqualTo(2);
-    assertThat(z.getValue("c")).isEqualTo(1);
-    assertThat(z.getValue("d")).isEqualTo(2);
+    assertThat(z.getValue("a")).isEqualTo(StarlarkInt.of(1));
+    assertThat(z.getValue("b")).isEqualTo(StarlarkInt.of(2));
+    assertThat(z.getValue("c")).isEqualTo(StarlarkInt.of(1));
+    assertThat(z.getValue("d")).isEqualTo(StarlarkInt.of(2));
   }
 
   @Test
@@ -1243,10 +1287,10 @@
         "  return x",
         "x = func()");
     StructImpl x = (StructImpl) ev.lookup("x");
-    assertThat(x.getValue("a")).isEqualTo(1);
-    assertThat(x.getValue("b")).isEqualTo(2);
-    assertThat(x.getValue("c")).isEqualTo(1);
-    assertThat(x.getValue("d")).isEqualTo(2);
+    assertThat(x.getValue("a")).isEqualTo(StarlarkInt.of(1));
+    assertThat(x.getValue("b")).isEqualTo(StarlarkInt.of(2));
+    assertThat(x.getValue("c")).isEqualTo(StarlarkInt.of(1));
+    assertThat(x.getValue("d")).isEqualTo(StarlarkInt.of(2));
   }
 
   @Test
@@ -1301,7 +1345,7 @@
         "s = struct(x = {'a' : 1})", //
         "s.x['b'] = 2\n");
     assertThat(((StructImpl) ev.lookup("s")).getValue("x"))
-        .isEqualTo(ImmutableMap.of("a", 1, "b", 2));
+        .isEqualTo(ImmutableMap.of("a", StarlarkInt.of(1), "b", StarlarkInt.of(2)));
   }
 
   @Test
@@ -1324,21 +1368,22 @@
                 StarlarkList.<Object>of(
                     mu,
                     StructProvider.STRUCT.create(
-                        ImmutableMap.<String, Object>of("x", Dict.<Object, Object>of(mu, 1, 1)),
+                        ImmutableMap.<String, Object>of(
+                            "x", Dict.<Object, Object>of(mu, StarlarkInt.of(1), StarlarkInt.of(1))),
                         "no field '%s'"),
                     Tuple.of()),
             "b", Tuple.of(),
-            "c", Dict.<Object, Object>of(mu, 2, 2)),
+            "c", Dict.<Object, Object>of(mu, StarlarkInt.of(2), StarlarkInt.of(2))),
         "no field '%s'");
   }
 
   @Test
   public void testStructMutabilityShallow() throws Exception {
-    assertThat(Starlark.isImmutable(makeStruct("a", 1))).isTrue();
+    assertThat(Starlark.isImmutable(makeStruct("a", StarlarkInt.of(1)))).isTrue();
   }
 
   private static StarlarkList<Object> makeList(@Nullable Mutability mu) {
-    return StarlarkList.<Object>of(mu, 1, 2, 3);
+    return StarlarkList.<Object>of(mu, StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3));
   }
 
   @Test
@@ -1356,7 +1401,7 @@
   @Test
   public void declaredProviders() throws Exception {
     evalAndExport(ev, "data = provider()", "d = data(x = 1, y ='abc')", "d_x = d.x", "d_y = d.y");
-    assertThat(ev.lookup("d_x")).isEqualTo(1);
+    assertThat(ev.lookup("d_x")).isEqualTo(StarlarkInt.of(1));
     assertThat(ev.lookup("d_y")).isEqualTo("abc");
     StarlarkProvider dataConstructor = (StarlarkProvider) ev.lookup("data");
     StructImpl data = (StructImpl) ev.lookup("d");
@@ -1376,7 +1421,7 @@
         "dxy = dx + dy",
         "x = dxy.x",
         "y = dxy.y");
-    assertThat(ev.lookup("x")).isEqualTo(1);
+    assertThat(ev.lookup("x")).isEqualTo(StarlarkInt.of(1));
     assertThat(ev.lookup("y")).isEqualTo("abc");
     StarlarkProvider dataConstructor = (StarlarkProvider) ev.lookup("data");
     StructImpl dx = (StructImpl) ev.lookup("dx");
@@ -1406,8 +1451,8 @@
         "d3 = d1 + d2",
         "f1 = d3.f1",
         "f2 = d3.f2");
-    assertThat(ev.lookup("f1")).isEqualTo(4);
-    assertThat(ev.lookup("f2")).isEqualTo(5);
+    assertThat(ev.lookup("f1")).isEqualTo(StarlarkInt.of(4));
+    assertThat(ev.lookup("f2")).isEqualTo(StarlarkInt.of(5));
   }
 
   @Test
@@ -1632,8 +1677,8 @@
     StarlarkInfo p1 = (StarlarkInfo) ev.lookup("p1");
 
     assertThat(p1.getProvider()).isEqualTo(p);
-    assertThat(ev.lookup("x")).isEqualTo(1);
-    assertThat(ev.lookup("y")).isEqualTo(2);
+    assertThat(ev.lookup("x")).isEqualTo(StarlarkInt.of(1));
+    assertThat(ev.lookup("y")).isEqualTo(StarlarkInt.of(2));
   }
 
   @Test
@@ -1648,8 +1693,8 @@
     StarlarkInfo p1 = (StarlarkInfo) ev.lookup("p1");
 
     assertThat(p1.getProvider()).isEqualTo(p);
-    assertThat(ev.lookup("x")).isEqualTo(1);
-    assertThat(ev.lookup("y")).isEqualTo(2);
+    assertThat(ev.lookup("x")).isEqualTo(StarlarkInt.of(1));
+    assertThat(ev.lookup("y")).isEqualTo(StarlarkInt.of(2));
   }
 
   @Test
@@ -1663,7 +1708,7 @@
     StarlarkInfo p1 = (StarlarkInfo) ev.lookup("p1");
 
     assertThat(p1.getProvider()).isEqualTo(p);
-    assertThat(ev.lookup("y")).isEqualTo(2);
+    assertThat(ev.lookup("y")).isEqualTo(StarlarkInt.of(2));
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java
index 52465e3..de47e1d 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java
@@ -64,6 +64,7 @@
 import net.starlark.java.eval.Mutability;
 import net.starlark.java.eval.Sequence;
 import net.starlark.java.eval.Starlark;
+import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.eval.StarlarkList;
 import org.junit.Before;
 import org.junit.Test;
@@ -2614,7 +2615,7 @@
             "BuildSettingInfo");
     StructImpl buildSettingInfo = (StructImpl) buildSetting.get(key);
 
-    assertThat(buildSettingInfo.getValue("value")).isEqualTo(24);
+    assertThat(buildSettingInfo.getValue("value")).isEqualTo(StarlarkInt.of(24));
   }
 
   @Test
@@ -2628,7 +2629,7 @@
             "BuildSettingInfo");
     StructImpl buildSettingInfo = (StructImpl) buildSetting.get(key);
 
-    assertThat(buildSettingInfo.getValue("value")).isEqualTo(42);
+    assertThat(buildSettingInfo.getValue("value")).isEqualTo(StarlarkInt.of(42));
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleImplementationFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleImplementationFunctionsTest.java
index aec46ba..f41a7c2 100644
--- a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleImplementationFunctionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleImplementationFunctionsTest.java
@@ -74,6 +74,7 @@
 import net.starlark.java.eval.Printer;
 import net.starlark.java.eval.Sequence;
 import net.starlark.java.eval.Starlark;
+import net.starlark.java.eval.StarlarkInt;
 import net.starlark.java.eval.StarlarkList;
 import net.starlark.java.eval.StarlarkThread;
 import org.junit.Before;
@@ -1511,7 +1512,7 @@
         .isEqualTo(
             new StarlarkProvider.Key(
                 Label.parseAbsolute("//test:foo.bzl", ImmutableMap.of()), "foo_provider"));
-    assertThat(((StructImpl) provider).getValue("a")).isEqualTo(123);
+    assertThat(((StructImpl) provider).getValue("a")).isEqualTo(StarlarkInt.of(123));
   }
 
   @Test
@@ -2311,15 +2312,15 @@
   public void testArgsAddInvalidTypesForArgAndValues() throws Exception {
     setRuleContext(createRuleContext("//foo:foo"));
     ev.checkEvalErrorContains(
-        "expected value of type 'string' for arg name, got 'Integer'",
+        "expected value of type 'string' for arg name, got 'int'",
         "args = ruleContext.actions.args()",
         "args.add(1, 'value')");
     ev.checkEvalErrorContains(
-        "expected value of type 'string' for arg name, got 'Integer'",
+        "expected value of type 'string' for arg name, got 'int'",
         "args = ruleContext.actions.args()",
         "args.add_all(1, [1, 2])");
     ev.checkEvalErrorContains(
-        "expected value of type 'sequence or depset' for values, got 'Integer'",
+        "expected value of type 'sequence or depset' for values, got 'int'",
         "args = ruleContext.actions.args()",
         "args.add_all(1)");
     ev.checkEvalErrorContains(
@@ -2417,7 +2418,7 @@
     CommandLineExpansionException e =
         assertThrows(CommandLineExpansionException.class, () -> action.getArguments());
     assertThat(e.getMessage())
-        .contains("Expected map_each to return string, None, or list of strings, found Integer");
+        .contains("Expected map_each to return string, None, or list of strings, found int");
   }
 
   @Test
diff --git a/src/test/java/net/starlark/java/eval/BUILD b/src/test/java/net/starlark/java/eval/BUILD
index a0de839..896039d 100644
--- a/src/test/java/net/starlark/java/eval/BUILD
+++ b/src/test/java/net/starlark/java/eval/BUILD
@@ -56,7 +56,6 @@
         "testdata/function.sky",
         "testdata/int.sky",
         "testdata/int_constructor.sky",
-        "testdata/int_function.sky",
         "testdata/list_mutation.sky",
         "testdata/list_slices.sky",
         "testdata/min_max.sky",
diff --git a/src/test/java/net/starlark/java/eval/EvalUtilsTest.java b/src/test/java/net/starlark/java/eval/EvalUtilsTest.java
index 0054d63..71bd1f7 100644
--- a/src/test/java/net/starlark/java/eval/EvalUtilsTest.java
+++ b/src/test/java/net/starlark/java/eval/EvalUtilsTest.java
@@ -17,6 +17,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.assertThrows;
 
+import java.math.BigInteger;
 import javax.annotation.Nullable;
 import net.starlark.java.annot.StarlarkBuiltin;
 import org.junit.Test;
@@ -61,12 +62,14 @@
   @Test
   public void testDatatypeMutabilityPrimitive() throws Exception {
     assertThat(Starlark.isImmutable("foo")).isTrue();
-    assertThat(Starlark.isImmutable(3)).isTrue();
+    assertThat(Starlark.isImmutable(StarlarkInt.of(3))).isTrue();
   }
 
   @Test
   public void testDatatypeMutabilityShallow() throws Exception {
-    assertThat(Starlark.isImmutable(Tuple.of(1, 2, 3))).isTrue();
+    assertThat(
+            Starlark.isImmutable(Tuple.of(StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3))))
+        .isTrue();
 
     assertThat(Starlark.isImmutable(makeList(null))).isTrue();
     assertThat(Starlark.isImmutable(makeDict(null))).isTrue();
@@ -91,15 +94,15 @@
 
     Object[] objects = {
       "1",
-      2,
+      StarlarkInt.of(2),
       true,
       Starlark.NONE,
-      Tuple.of(1, 2, 3),
+      Tuple.of(StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3)),
       Tuple.of("1", "2", "3"),
-      StarlarkList.of(mu, 1, 2, 3),
+      StarlarkList.of(mu, StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3)),
       StarlarkList.of(mu, "1", "2", "3"),
-      Dict.of(mu, "key", 123),
-      Dict.of(mu, 123, "value"),
+      Dict.of(mu, "key", StarlarkInt.of(123)),
+      Dict.of(mu, StarlarkInt.of(123), "value"),
       myValue,
     };
 
@@ -126,10 +129,47 @@
   @Test
   public void testLen() {
     assertThat(Starlark.len("abc")).isEqualTo(3);
-    assertThat(Starlark.len(Tuple.of(1, 2, 3))).isEqualTo(3);
-    assertThat(Starlark.len(StarlarkList.of(null, 1, 2, 3))).isEqualTo(3);
-    assertThat(Starlark.len(Dict.of(null, "one", 1, "two", 2))).isEqualTo(2);
+    assertThat(Starlark.len(Tuple.of(StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3))))
+        .isEqualTo(3);
+    assertThat(
+            Starlark.len(
+                StarlarkList.of(null, StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3))))
+        .isEqualTo(3);
+    assertThat(Starlark.len(Dict.of(null, "one", StarlarkInt.of(1), "two", StarlarkInt.of(2))))
+        .isEqualTo(2);
     assertThat(Starlark.len(true)).isEqualTo(-1);
     assertThrows(IllegalArgumentException.class, () -> Starlark.len(this));
   }
+
+  @Test
+  public void testIntConstructor() throws Exception {
+    // small values are cached
+    assertThat(StarlarkInt.of(-1)).isSameInstanceAs(StarlarkInt.of(-1));
+    assertThat(StarlarkInt.of(0)).isSameInstanceAs(StarlarkInt.ZERO);
+    assertThat(StarlarkInt.of(123)).isSameInstanceAs(StarlarkInt.of(123));
+    // int32
+    assertThat(StarlarkInt.of(0).getClass().getSimpleName()).isEqualTo("Int32");
+    assertThat(StarlarkInt.of(Integer.MAX_VALUE).getClass().getSimpleName()).isEqualTo("Int32");
+    assertThat(StarlarkInt.of(Integer.MIN_VALUE).getClass().getSimpleName()).isEqualTo("Int32");
+    // int64
+    assertThat(StarlarkInt.of((long) Integer.MAX_VALUE + 1).getClass().getSimpleName())
+        .isEqualTo("Int64");
+    assertThat(StarlarkInt.of((long) Integer.MIN_VALUE - 1).getClass().getSimpleName())
+        .isEqualTo("Int64");
+    assertThat(StarlarkInt.of(Long.MAX_VALUE).getClass().getSimpleName()).isEqualTo("Int64");
+    assertThat(StarlarkInt.of(Long.MIN_VALUE).getClass().getSimpleName()).isEqualTo("Int64");
+    // big
+    assertThat(StarlarkInt.of(new BigInteger("7fffffffffffffff", 16)).getClass().getSimpleName())
+        .isEqualTo("Int64"); // (max long)
+    assertThat(StarlarkInt.of(new BigInteger("8000000000000000", 16)).getClass().getSimpleName())
+        .isEqualTo("Big");
+    assertThat(StarlarkInt.of(new BigInteger("8000000000000001", 16)).getClass().getSimpleName())
+        .isEqualTo("Big");
+    assertThat(StarlarkInt.of(new BigInteger("-7fffffffffffffff", 16)).getClass().getSimpleName())
+        .isEqualTo("Int64");
+    assertThat(StarlarkInt.of(new BigInteger("-8000000000000000", 16)).getClass().getSimpleName())
+        .isEqualTo("Int64"); // (min long)
+    assertThat(StarlarkInt.of(new BigInteger("-8000000000000001", 16)).getClass().getSimpleName())
+        .isEqualTo("Big");
+  }
 }
diff --git a/src/test/java/net/starlark/java/eval/EvaluationTest.java b/src/test/java/net/starlark/java/eval/EvaluationTest.java
index 3020e60..f1c47f8 100644
--- a/src/test/java/net/starlark/java/eval/EvaluationTest.java
+++ b/src/test/java/net/starlark/java/eval/EvaluationTest.java
@@ -170,17 +170,19 @@
         .testExpression("'%sx' % 'foo' + 'bar1'", "fooxbar1")
         .testExpression("('%sx' % 'foo') + 'bar2'", "fooxbar2")
         .testExpression("'%sx' % ('foo' + 'bar3')", "foobar3x")
-        .testExpression("123 + 456", 579)
-        .testExpression("456 - 123", 333)
-        .testExpression("8 % 3", 2)
+        .testExpression("123 + 456", StarlarkInt.of(579))
+        .testExpression("456 - 123", StarlarkInt.of(333))
+        .testExpression("8 % 3", StarlarkInt.of(2))
         .testIfErrorContains("unsupported binary operation: int % string", "3 % 'foo'")
-        .testExpression("-5", -5)
+        .testExpression("-5", StarlarkInt.of(-5))
         .testIfErrorContains("unsupported unary operation: -string", "-'foo'");
   }
 
   @Test
   public void testListExprs() throws Exception {
-    ev.new Scenario().testExactOrder("[1, 2, 3]", 1, 2, 3).testExactOrder("(1, 2, 3)", 1, 2, 3);
+    ev.new Scenario()
+        .testExactOrder("[1, 2, 3]", StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3))
+        .testExactOrder("(1, 2, 3)", StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3));
   }
 
   @Test
@@ -191,9 +193,9 @@
   @Test
   public void testConditionalExpressions() throws Exception {
     ev.new Scenario()
-        .testExpression("1 if True else 2", 1)
-        .testExpression("1 if False else 2", 2)
-        .testExpression("1 + 2 if 3 + 4 else 5 + 6", 3);
+        .testExpression("1 if True else 2", StarlarkInt.of(1))
+        .testExpression("1 if False else 2", StarlarkInt.of(2))
+        .testExpression("1 + 2 if 3 + 4 else 5 + 6", StarlarkInt.of(3));
   }
 
   @Test
@@ -226,10 +228,11 @@
           }
 
           @Override
-          public Object fastcall(StarlarkThread thread, Object[] positional, Object[] named) {
-            int sum = 0;
+          public StarlarkInt fastcall(StarlarkThread thread, Object[] positional, Object[] named)
+              throws EvalException {
+            StarlarkInt sum = StarlarkInt.of(0);
             for (Object arg : positional) {
-              sum += (Integer) arg;
+              sum = StarlarkInt.add(sum, (StarlarkInt) arg);
             }
             return sum;
           }
@@ -237,18 +240,18 @@
 
     ev.new Scenario()
         .update(sum.getName(), sum)
-        .testExpression("sum(1, 2, 3, 4, 5, 6)", 21)
+        .testExpression("sum(1, 2, 3, 4, 5, 6)", StarlarkInt.of(21))
         .testExpression("sum", sum)
-        .testExpression("sum(a=1, b=2)", 0);
+        .testExpression("sum(a=1, b=2)", StarlarkInt.of(0));
   }
 
   @Test
   public void testNotCallInt() throws Exception {
     ev.new Scenario()
         .setUp("sum = 123456")
-        .testLookup("sum", 123456)
+        .testLookup("sum", StarlarkInt.of(123456))
         .testIfExactError("'int' object is not callable", "sum(1, 2, 3, 4, 5, 6)")
-        .testExpression("sum", 123456);
+        .testExpression("sum", StarlarkInt.of(123456));
   }
 
   @Test
@@ -290,19 +293,19 @@
   @Test
   public void testModulo() throws Exception {
     ev.new Scenario()
-        .testExpression("6 % 2", 0)
-        .testExpression("6 % 4", 2)
-        .testExpression("3 % 6", 3)
-        .testExpression("7 % -4", -1)
-        .testExpression("-7 % 4", 1)
-        .testExpression("-7 % -4", -3)
+        .testExpression("6 % 2", StarlarkInt.of(0))
+        .testExpression("6 % 4", StarlarkInt.of(2))
+        .testExpression("3 % 6", StarlarkInt.of(3))
+        .testExpression("7 % -4", StarlarkInt.of(-1))
+        .testExpression("-7 % 4", StarlarkInt.of(1))
+        .testExpression("-7 % -4", StarlarkInt.of(-3))
         .testIfExactError("integer modulo by zero", "5 % 0");
   }
 
   @Test
   public void testMult() throws Exception {
     ev.new Scenario()
-        .testExpression("6 * 7", 42)
+        .testExpression("6 * 7", StarlarkInt.of(42))
         .testExpression("3 * 'ab'", "ababab")
         .testExpression("0 * 'ab'", "")
         .testExpression("'1' + '0' * 5", "100000")
@@ -318,35 +321,38 @@
   @Test
   public void testFloorDivision() throws Exception {
     ev.new Scenario()
-        .testExpression("6 // 2", 3)
-        .testExpression("6 // 4", 1)
-        .testExpression("3 // 6", 0)
-        .testExpression("7 // -2", -4)
-        .testExpression("-7 // 2", -4)
-        .testExpression("-7 // -2", 3)
-        .testExpression("2147483647 // 2", 1073741823)
+        .testExpression("6 // 2", StarlarkInt.of(3))
+        .testExpression("6 // 4", StarlarkInt.of(1))
+        .testExpression("3 // 6", StarlarkInt.of(0))
+        .testExpression("7 // -2", StarlarkInt.of(-4))
+        .testExpression("-7 // 2", StarlarkInt.of(-4))
+        .testExpression("-7 // -2", StarlarkInt.of(3))
+        .testExpression("2147483647 // 2", StarlarkInt.of(1073741823))
         .testIfErrorContains("unsupported binary operation: string // int", "'str' // 2")
         .testIfExactError("integer division by zero", "5 // 0");
   }
 
   @Test
-  public void testCheckedArithmetic() throws Exception {
+  public void testArithmeticDoesNotOverflow() throws Exception {
     ev.new Scenario()
-        .testIfErrorContains("integer overflow", "2000000000 + 2000000000")
-        .testIfErrorContains("integer overflow", "1234567890 * 987654321")
-        .testIfErrorContains("integer overflow", "- 2000000000 - 2000000000")
+        .testEval("2000000000 + 2000000000", "1000000000 + 1000000000 + 1000000000 + 1000000000")
+        .testExpression("1234567890 * 987654321", StarlarkInt.of(1219326311126352690L))
+        .testExpression(
+            "1234567890 * 987654321 * 987654321",
+            StarlarkInt.multiply(StarlarkInt.of(1219326311126352690L), StarlarkInt.of(987654321)))
+        .testEval("- 2000000000 - 2000000000", "-1000000000 - 1000000000 - 1000000000 - 1000000000")
 
         // literal 2147483648 is not allowed, so we compute it
         .setUp("minint = - 2147483647 - 1")
-        .testIfErrorContains("integer overflow", "-minint");
+        .testEval("-minint", "2147483647+1");
   }
 
   @Test
   public void testOperatorPrecedence() throws Exception {
     ev.new Scenario()
-        .testExpression("2 + 3 * 4", 14)
-        .testExpression("2 + 3 // 4", 2)
-        .testExpression("2 * 3 + 4 // -2", 4);
+        .testExpression("2 + 3 * 4", StarlarkInt.of(14))
+        .testExpression("2 + 3 // 4", StarlarkInt.of(2))
+        .testExpression("2 * 3 + 4 // -2", StarlarkInt.of(4));
   }
 
   @Test
@@ -357,22 +363,38 @@
   @Test
   public void testConcatLists() throws Exception {
     ev.new Scenario()
-        .testExactOrder("[1,2] + [3,4]", 1, 2, 3, 4)
-        .testExactOrder("(1,2)", 1, 2)
-        .testExactOrder("(1,2) + (3,4)", 1, 2, 3, 4);
+        .testExactOrder(
+            "[1,2] + [3,4]",
+            StarlarkInt.of(1),
+            StarlarkInt.of(2),
+            StarlarkInt.of(3),
+            StarlarkInt.of(4))
+        .testExactOrder("(1,2)", StarlarkInt.of(1), StarlarkInt.of(2))
+        .testExactOrder(
+            "(1,2) + (3,4)",
+            StarlarkInt.of(1),
+            StarlarkInt.of(2),
+            StarlarkInt.of(3),
+            StarlarkInt.of(4));
 
     // TODO(fwe): cannot be handled by current testing suite
     // list
     Object x = ev.eval("[1,2] + [3,4]");
-    assertThat((Iterable<?>) x).containsExactly(1, 2, 3, 4).inOrder();
+    assertThat((Iterable<?>) x)
+        .containsExactly(StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3), StarlarkInt.of(4))
+        .inOrder();
     assertThat(x).isInstanceOf(StarlarkList.class);
     assertThat(Starlark.isImmutable(x)).isFalse();
 
     // tuple
     x = ev.eval("(1,2) + (3,4)");
-    assertThat((Iterable<?>) x).containsExactly(1, 2, 3, 4).inOrder();
+    assertThat((Iterable<?>) x)
+        .containsExactly(StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3), StarlarkInt.of(4))
+        .inOrder();
     assertThat(x).isInstanceOf(Tuple.class);
-    assertThat(x).isEqualTo(Tuple.of(1, 2, 3, 4));
+    assertThat(x)
+        .isEqualTo(
+            Tuple.of(StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3), StarlarkInt.of(4)));
     assertThat(Starlark.isImmutable(x)).isTrue();
 
     ev.checkEvalError("unsupported binary operation: tuple + list", "(1,2) + [3,4]");
@@ -416,15 +438,20 @@
             "bar/wiz.cc",
             "bar/quux.java",
             "bar/quux.cc")
-        .testExactOrder("[i for i in (1, 2)]", 1, 2)
-        .testExactOrder("[i for i in [2, 3] or [1, 2]]", 2, 3);
+        .testExactOrder("[i for i in (1, 2)]", StarlarkInt.of(1), StarlarkInt.of(2))
+        .testExactOrder("[i for i in [2, 3] or [1, 2]]", StarlarkInt.of(2), StarlarkInt.of(3));
   }
 
   @Test
   public void testNestedListComprehensions() throws Exception {
     ev.new Scenario()
         .setUp("li = [[1, 2], [3, 4]]")
-        .testExactOrder("[j for i in li for j in i]", 1, 2, 3, 4);
+        .testExactOrder(
+            "[j for i in li for j in i]",
+            StarlarkInt.of(1),
+            StarlarkInt.of(2),
+            StarlarkInt.of(3),
+            StarlarkInt.of(4));
     ev.new Scenario()
         .setUp("input = [['abc'], ['def', 'ghi']]\n")
         .testExactOrder(
@@ -566,8 +593,8 @@
   public void testTupleDestructuring() throws Exception {
     ev.new Scenario()
         .setUp("a, b = 1, 2")
-        .testLookup("a", 1)
-        .testLookup("b", 2)
+        .testLookup("a", StarlarkInt.of(1))
+        .testLookup("b", StarlarkInt.of(2))
         .setUp("c, d = {'key1':2, 'key2':3}")
         .testLookup("c", "key1")
         .testLookup("d", "key2");
@@ -575,20 +602,20 @@
 
   @Test
   public void testSingleTuple() throws Exception {
-    ev.new Scenario().setUp("(a,) = [1]").testLookup("a", 1);
+    ev.new Scenario().setUp("(a,) = [1]").testLookup("a", StarlarkInt.of(1));
   }
 
   @Test
   public void testHeterogeneousDict() throws Exception {
     ev.new Scenario()
         .setUp("d = {'str': 1, 2: 3}", "a = d['str']", "b = d[2]")
-        .testLookup("a", 1)
-        .testLookup("b", 3);
+        .testLookup("a", StarlarkInt.of(1))
+        .testLookup("b", StarlarkInt.of(3));
   }
 
   @Test
   public void testAccessDictWithATupleKey() throws Exception {
-    ev.new Scenario().setUp("x = {(1, 2): 3}[1, 2]").testLookup("x", 3);
+    ev.new Scenario().setUp("x = {(1, 2): 3}[1, 2]").testLookup("x", StarlarkInt.of(3));
   }
 
   @Test
@@ -602,26 +629,29 @@
   public void testRecursiveTupleDestructuring() throws Exception {
     ev.new Scenario()
         .setUp("((a, b), (c, d)) = [(1, 2), (3, 4)]")
-        .testLookup("a", 1)
-        .testLookup("b", 2)
-        .testLookup("c", 3)
-        .testLookup("d", 4);
+        .testLookup("a", StarlarkInt.of(1))
+        .testLookup("b", StarlarkInt.of(2))
+        .testLookup("c", StarlarkInt.of(3))
+        .testLookup("d", StarlarkInt.of(4));
   }
 
   @Test
   public void testListComprehensionAtTopLevel() throws Exception {
     // It is allowed to have a loop variable with the same name as a global variable.
     ev.new Scenario()
-        .update("x", 42)
+        .update("x", StarlarkInt.of(42))
         .setUp("y = [x + 1 for x in [1,2,3]]")
-        .testExactOrder("y", 2, 3, 4);
+        .testExactOrder("y", StarlarkInt.of(2), StarlarkInt.of(3), StarlarkInt.of(4));
   }
 
   @Test
   public void testDictComprehensions() throws Exception {
     ev.new Scenario()
         .testExpression("{a : a for a in []}", Collections.emptyMap())
-        .testExpression("{b : b for b in [1, 2]}", ImmutableMap.of(1, 1, 2, 2))
+        .testExpression(
+            "{b : b for b in [1, 2]}",
+            ImmutableMap.of(
+                StarlarkInt.of(1), StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(2)))
         .testExpression(
             "{c : 'v_' + c for c in ['a', 'b']}", ImmutableMap.of("a", "v_a", "b", "v_b"))
         .testExpression(
@@ -629,7 +659,9 @@
         .testExpression(
             "{'k_' + e : 'v_' + e for e in ['a', 'b']}",
             ImmutableMap.of("k_a", "v_a", "k_b", "v_b"))
-        .testExpression("{x+y : x*y for x, y in [[2, 3]]}", ImmutableMap.of(5, 6));
+        .testExpression(
+            "{x+y : x*y for x, y in [[2, 3]]}",
+            ImmutableMap.of(StarlarkInt.of(5), StarlarkInt.of(6)));
   }
 
   @Test
@@ -642,13 +674,24 @@
     ev.new Scenario()
         .testExpression(
             "{x : x * y for x in range(1, 10) if x % 2 == 0 for y in range(1, 10) if y == x}",
-            ImmutableMap.of(2, 4, 4, 16, 6, 36, 8, 64));
+            ImmutableMap.of(
+                StarlarkInt.of(2),
+                StarlarkInt.of(4),
+                StarlarkInt.of(4),
+                StarlarkInt.of(16),
+                StarlarkInt.of(6),
+                StarlarkInt.of(36),
+                StarlarkInt.of(8),
+                StarlarkInt.of(64)));
   }
 
   @Test
   public void testDictComprehensions_multipleKey() throws Exception {
     ev.new Scenario()
-        .testExpression("{x : x for x in [1, 2, 1]}", ImmutableMap.of(1, 1, 2, 2))
+        .testExpression(
+            "{x : x for x in [1, 2, 1]}",
+            ImmutableMap.of(
+                StarlarkInt.of(1), StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(2)))
         .testExpression(
             "{y : y for y in ['ab', 'c', 'a' + 'b']}", ImmutableMap.of("ab", "ab", "c", "c"));
   }
@@ -656,45 +699,44 @@
   @Test
   public void testListConcatenation() throws Exception {
     ev.new Scenario()
-        .testExpression("[1, 2] + [3, 4]", StarlarkList.of(null, 1, 2, 3, 4))
-        .testExpression("(1, 2) + (3, 4)", Tuple.of(1, 2, 3, 4))
+        .testEval("[1, 2] + [3, 4]", "[1, 2, 3, 4]")
+        .testEval("(1, 2) + (3, 4)", "(1, 2, 3, 4)")
         .testIfExactError("unsupported binary operation: list + tuple", "[1, 2] + (3, 4)")
         .testIfExactError("unsupported binary operation: tuple + list", "(1, 2) + [3, 4]");
   }
 
   @Test
   public void testListMultiply() throws Exception {
-    Mutability mu = Mutability.create("test");
     ev.new Scenario()
-        .testExpression("[1, 2, 3] * 1", StarlarkList.of(mu, 1, 2, 3))
-        .testExpression("[1, 2] * 2", StarlarkList.of(mu, 1, 2, 1, 2))
-        .testExpression("[1, 2] * 3", StarlarkList.of(mu, 1, 2, 1, 2, 1, 2))
-        .testExpression("[1, 2] * 4", StarlarkList.of(mu, 1, 2, 1, 2, 1, 2, 1, 2))
-        .testExpression("[8] * 5", StarlarkList.of(mu, 8, 8, 8, 8, 8))
-        .testExpression("[    ] * 10", StarlarkList.empty())
-        .testExpression("[1, 2] * 0", StarlarkList.empty())
-        .testExpression("[1, 2] * -4", StarlarkList.empty())
-        .testExpression("2 * [1, 2]", StarlarkList.of(mu, 1, 2, 1, 2))
-        .testExpression("10 * []", StarlarkList.empty())
-        .testExpression("0 * [1, 2]", StarlarkList.empty())
-        .testExpression("-4 * [1, 2]", StarlarkList.empty());
+        .testEval("[1, 2, 3] * 1", "[1, 2, 3]")
+        .testEval("[1, 2] * 2", "[1, 2, 1, 2]")
+        .testEval("[1, 2] * 3", "[1, 2, 1, 2, 1, 2]")
+        .testEval("[1, 2] * 4", "[1, 2, 1, 2, 1, 2, 1, 2]")
+        .testEval("[8] * 5", "[8, 8, 8, 8, 8]")
+        .testEval("[    ] * 10", "[]")
+        .testEval("[1, 2] * 0", "[]")
+        .testEval("[1, 2] * -4", "[]")
+        .testEval("2 * [1, 2]", "[1, 2, 1, 2]")
+        .testEval("10 * []", "[]")
+        .testEval("0 * [1, 2]", "[]")
+        .testEval("-4 * [1, 2]", "[]");
   }
 
   @Test
   public void testTupleMultiply() throws Exception {
     ev.new Scenario()
-        .testExpression("(1, 2, 3) * 1", Tuple.of(1, 2, 3))
-        .testExpression("(1, 2) * 2", Tuple.of(1, 2, 1, 2))
-        .testExpression("(1, 2) * 3", Tuple.of(1, 2, 1, 2, 1, 2))
-        .testExpression("(1, 2) * 4", Tuple.of(1, 2, 1, 2, 1, 2, 1, 2))
-        .testExpression("(8,) * 5", Tuple.of(8, 8, 8, 8, 8))
-        .testExpression("(    ) * 10", Tuple.empty())
-        .testExpression("(1, 2) * 0", Tuple.empty())
-        .testExpression("(1, 2) * -4", Tuple.empty())
-        .testExpression("2 * (1, 2)", Tuple.of(1, 2, 1, 2))
-        .testExpression("10 * ()", Tuple.empty())
-        .testExpression("0 * (1, 2)", Tuple.empty())
-        .testExpression("-4 * (1, 2)", Tuple.empty());
+        .testEval("(1, 2, 3) * 1", "(1, 2, 3)")
+        .testEval("(1, 2) * 2", "(1, 2, 1, 2)")
+        .testEval("(1, 2) * 3", "(1, 2, 1, 2, 1, 2)")
+        .testEval("(1, 2) * 4", "(1, 2, 1, 2, 1, 2, 1, 2)")
+        .testEval("(8,) * 5", "(8, 8, 8, 8, 8)")
+        .testEval("(    ) * 10", "()")
+        .testEval("(1, 2) * 0", "()")
+        .testEval("(1, 2) * -4", "()")
+        .testEval("2 * (1, 2)", "(1, 2, 1, 2)")
+        .testEval("10 * ()", "()")
+        .testEval("0 * (1, 2)", "()")
+        .testEval("-4 * (1, 2)", "()");
   }
 
   @Test
@@ -809,7 +851,7 @@
 
   @Test
   public void testInCompositeForPrecedence() throws Exception {
-    ev.new Scenario().testExpression("not 'a' in ['a'] or 0", 0);
+    ev.new Scenario().testExpression("not 'a' in ['a'] or 0", StarlarkInt.of(0));
   }
 
   private static StarlarkValue createObjWithStr() {
@@ -913,6 +955,15 @@
       Starlark.execFile(input, FileOptions.DEFAULT, module, thread);
     }
     assertThat(module.getGlobal("x"))
-        .isEqualTo(StarlarkList.of(/*mutability=*/ null, 1, 2, "foo", 4, 1, 2, "foo1"));
+        .isEqualTo(
+            StarlarkList.of(
+                /*mutability=*/ null,
+                StarlarkInt.of(1),
+                StarlarkInt.of(2),
+                "foo",
+                StarlarkInt.of(4),
+                StarlarkInt.of(1),
+                StarlarkInt.of(2),
+                "foo1"));
   }
 }
diff --git a/src/test/java/net/starlark/java/eval/FunctionTest.java b/src/test/java/net/starlark/java/eval/FunctionTest.java
index 6aeb4c7..519cb52 100644
--- a/src/test/java/net/starlark/java/eval/FunctionTest.java
+++ b/src/test/java/net/starlark/java/eval/FunctionTest.java
@@ -38,7 +38,9 @@
         .inOrder();
     assertThat(f.hasVarargs()).isTrue();
     assertThat(f.hasKwargs()).isTrue();
-    assertThat(getDefaults(f)).containsExactly(null, 1, null, 2, null, null).inOrder();
+    assertThat(getDefaults(f))
+        .containsExactly(null, StarlarkInt.of(1), null, StarlarkInt.of(2), null, null)
+        .inOrder();
 
     // same, sans varargs
     ev.exec("def g(a, b=1, *, c, d=2, **kwargs): pass");
@@ -46,7 +48,9 @@
     assertThat(g.getParameterNames()).containsExactly("a", "b", "c", "d", "kwargs").inOrder();
     assertThat(g.hasVarargs()).isFalse();
     assertThat(g.hasKwargs()).isTrue();
-    assertThat(getDefaults(g)).containsExactly(null, 1, null, 2, null).inOrder();
+    assertThat(getDefaults(g))
+        .containsExactly(null, StarlarkInt.of(1), null, StarlarkInt.of(2), null)
+        .inOrder();
   }
 
   private static List<Object> getDefaults(StarlarkFunction fn) {
@@ -66,7 +70,7 @@
         "  outer_func(a)",
         "func(1)",
         "func(2)");
-    assertThat(params).containsExactly(1, 2).inOrder();
+    assertThat(params).containsExactly(StarlarkInt.of(1), StarlarkInt.of(2)).inOrder();
   }
 
   private void createOuterFunction(final List<Object> params) throws Exception {
@@ -90,12 +94,12 @@
 
   @Test
   public void testFunctionDefNoEffectOutsideScope() throws Exception {
-    ev.update("a", 1);
+    ev.update("a", StarlarkInt.of(1));
     ev.exec(
         "def func():", //
         "  a = 2",
         "func()\n");
-    assertThat(ev.lookup("a")).isEqualTo(1);
+    assertThat(ev.lookup("a")).isEqualTo(StarlarkInt.of(1));
   }
 
   @Test
@@ -106,7 +110,7 @@
         "  b = a",
         "  return b",
         "c = func()\n");
-    assertThat(ev.lookup("c")).isEqualTo(1);
+    assertThat(ev.lookup("c")).isEqualTo(StarlarkInt.of(1));
   }
 
   @Test
@@ -118,7 +122,7 @@
         "  b = a",
         "  return b",
         "c = func()\n");
-    assertThat(ev.lookup("c")).isEqualTo(2);
+    assertThat(ev.lookup("c")).isEqualTo(StarlarkInt.of(2));
   }
 
   @Test
@@ -156,14 +160,16 @@
         "  a = 3",
         "  return b",
         "c = func()\n");
-    assertThat(ev.lookup("c")).isEqualTo(2);
+    assertThat(ev.lookup("c")).isEqualTo(StarlarkInt.of(2));
   }
 
   @SuppressWarnings("unchecked")
   @Test
   public void testStarlarkGlobalComprehensionIsAllowed() throws Exception {
     ev.exec("a = [i for i in [1, 2, 3]]\n");
-    assertThat((Iterable<Object>) ev.lookup("a")).containsExactly(1, 2, 3).inOrder();
+    assertThat((Iterable<Object>) ev.lookup("a"))
+        .containsExactly(StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3))
+        .inOrder();
   }
 
   @Test
@@ -172,7 +178,7 @@
         "def func():", //
         "  return 2",
         "b = func()\n");
-    assertThat(ev.lookup("b")).isEqualTo(2);
+    assertThat(ev.lookup("b")).isEqualTo(StarlarkInt.of(2));
   }
 
   @Test
@@ -182,7 +188,7 @@
         "  for i in [1, 2, 3, 4, 5]:",
         "    return i",
         "b = func()\n");
-    assertThat(ev.lookup("b")).isEqualTo(1);
+    assertThat(ev.lookup("b")).isEqualTo(StarlarkInt.of(1));
   }
 
   @Test
@@ -195,8 +201,8 @@
         "  return b",
         "c = func(0)",
         "d = func(1)\n");
-    assertThat(ev.lookup("c")).isEqualTo(1);
-    assertThat(ev.lookup("d")).isEqualTo(2);
+    assertThat(ev.lookup("c")).isEqualTo(StarlarkInt.of(1));
+    assertThat(ev.lookup("d")).isEqualTo(StarlarkInt.of(2));
   }
 
   @Test
@@ -210,7 +216,7 @@
         "  func2(b)",
         "func1(1)",
         "func1(2)\n");
-    assertThat(params).containsExactly(1, 2).inOrder();
+    assertThat(params).containsExactly(StarlarkInt.of(1), StarlarkInt.of(2)).inOrder();
   }
 
   @Test
@@ -222,7 +228,7 @@
         "def func1():",
         "  return func2()",
         "b = func1()\n");
-    assertThat(ev.lookup("b")).isEqualTo(1);
+    assertThat(ev.lookup("b")).isEqualTo(StarlarkInt.of(1));
   }
 
   @Test
@@ -235,7 +241,7 @@
         "  dummy = a",
         "  return func2(2)",
         "b = func1()\n");
-    assertThat(ev.lookup("b")).isEqualTo(0);
+    assertThat(ev.lookup("b")).isEqualTo(StarlarkInt.of(0));
   }
 
   @Test
@@ -252,7 +258,7 @@
         "def func(): return {'a' : 1}", //
         "d = func()",
         "a = d['a']\n");
-    assertThat(ev.lookup("a")).isEqualTo(1);
+    assertThat(ev.lookup("a")).isEqualTo(StarlarkInt.of(1));
   }
 
   @Test
@@ -261,7 +267,7 @@
         "def func(): return [1, 2, 3]", //
         "d = func()",
         "a = d[1]\n");
-    assertThat(ev.lookup("a")).isEqualTo(2);
+    assertThat(ev.lookup("a")).isEqualTo(StarlarkInt.of(2));
   }
 
   @Test
@@ -271,7 +277,7 @@
         "  return a + 1",
         "alias = func",
         "r = alias(1)");
-    assertThat(ev.lookup("r")).isEqualTo(2);
+    assertThat(ev.lookup("r")).isEqualTo(StarlarkInt.of(2));
   }
 
   @Test
@@ -280,7 +286,7 @@
         "def func(a, b, c):", //
         "  return a + b + c",
         "v = func(1, c = 2, b = 3)");
-    assertThat(ev.lookup("v")).isEqualTo(6);
+    assertThat(ev.lookup("v")).isEqualTo(StarlarkInt.of(6));
   }
 
   private String functionWithOptionalArgs() {
@@ -448,7 +454,7 @@
         "  a = 3",
         "  return foo()",
         "v = bar()\n");
-    assertThat(ev.lookup("v")).isEqualTo(2);
+    assertThat(ev.lookup("v")).isEqualTo(StarlarkInt.of(2));
   }
 
   @Test
diff --git a/src/test/java/net/starlark/java/eval/MethodLibraryTest.java b/src/test/java/net/starlark/java/eval/MethodLibraryTest.java
index ede15a6..775be58 100644
--- a/src/test/java/net/starlark/java/eval/MethodLibraryTest.java
+++ b/src/test/java/net/starlark/java/eval/MethodLibraryTest.java
@@ -312,13 +312,13 @@
   public void testDictionaryAccess() throws Exception {
     ev.new Scenario()
         .testEval("{1: ['foo']}[1]", "['foo']")
-        .testExpression("{'4': 8}['4']", 8)
+        .testExpression("{'4': 8}['4']", StarlarkInt.of(8))
         .testExpression("{'a': 'aa', 'b': 'bb', 'c': 'cc'}['b']", "bb");
   }
 
   @Test
   public void testDictionaryVariableAccess() throws Exception {
-    ev.new Scenario().setUp("d = {'a' : 1}", "a = d['a']").testLookup("a", 1);
+    ev.new Scenario().setUp("d = {'a' : 1}", "a = d['a']").testLookup("a", StarlarkInt.of(1));
   }
 
   @Test
@@ -482,12 +482,12 @@
   @Test
   public void testListIndexMethod() throws Exception {
     ev.new Scenario()
-        .testExpression("['a', 'b', 'c'].index('a')", 0)
-        .testExpression("['a', 'b', 'c'].index('b')", 1)
-        .testExpression("['a', 'b', 'c'].index('c')", 2)
-        .testExpression("[2, 4, 6].index(4)", 1)
-        .testExpression("[2, 4, 6].index(4)", 1)
-        .testExpression("[0, 1, [1]].index([1])", 2)
+        .testExpression("['a', 'b', 'c'].index('a')", StarlarkInt.of(0))
+        .testExpression("['a', 'b', 'c'].index('b')", StarlarkInt.of(1))
+        .testExpression("['a', 'b', 'c'].index('c')", StarlarkInt.of(2))
+        .testExpression("[2, 4, 6].index(4)", StarlarkInt.of(1))
+        .testExpression("[2, 4, 6].index(4)", StarlarkInt.of(1))
+        .testExpression("[0, 1, [1]].index([1])", StarlarkInt.of(2))
         .testIfErrorContains("item \"a\" not found in list", "[1, 2].index('a')")
         .testIfErrorContains("item 0 not found in list", "[].index(0)");
   }
@@ -496,8 +496,8 @@
   public void testHash() throws Exception {
     // We specify the same string hashing algorithm as String.hashCode().
     ev.new Scenario()
-        .testExpression("hash('starlark')", "starlark".hashCode())
-        .testExpression("hash('google')", "google".hashCode())
+        .testExpression("hash('starlark')", StarlarkInt.of("starlark".hashCode()))
+        .testExpression("hash('google')", StarlarkInt.of("google".hashCode()))
         .testIfErrorContains(
             "in call to hash(), parameter 'value' got value of type 'NoneType', want 'string'",
             "hash(None)");
@@ -507,7 +507,7 @@
   public void testRangeType() throws Exception {
     ev.new Scenario()
         .setUp("a = range(3)")
-        .testExpression("len(a)", 3)
+        .testExpression("len(a)", StarlarkInt.of(3))
         .testExpression("str(a)", "range(0, 3)")
         .testExpression("str(range(1,2,3))", "range(1, 2, 3)")
         .testExpression("repr(a)", "range(0, 3)")
@@ -529,11 +529,11 @@
         .testExpression("str(list(range(5, 0, -1)))", "[5, 4, 3, 2, 1]")
         .testExpression("str(list(range(5, 0, -10)))", "[5]")
         .testExpression("str(list(range(0, -3, -2)))", "[0, -2]")
-        .testExpression("range(3)[-1]", 2)
+        .testExpression("range(3)[-1]", StarlarkInt.of(2))
         .testIfErrorContains(
             "index out of range (index is 3, but sequence has 3 elements)", "range(3)[3]")
         .testExpression("str(range(5)[1:])", "range(1, 5)")
-        .testExpression("len(range(5)[1:])", 4)
+        .testExpression("len(range(5)[1:])", StarlarkInt.of(4))
         .testExpression("str(range(5)[:2])", "range(0, 2)")
         .testExpression("str(range(10)[1:9:2])", "range(1, 9, 2)")
         .testExpression("str(list(range(10)[1:9:2]))", "[1, 3, 5, 7]")
@@ -600,17 +600,17 @@
 
   @Test
   public void testLenOnString() throws Exception {
-    ev.new Scenario().testExpression("len('abc')", 3);
+    ev.new Scenario().testExpression("len('abc')", StarlarkInt.of(3));
   }
 
   @Test
   public void testLenOnList() throws Exception {
-    ev.new Scenario().testExpression("len([1,2,3])", 3);
+    ev.new Scenario().testExpression("len([1,2,3])", StarlarkInt.of(3));
   }
 
   @Test
   public void testLenOnDict() throws Exception {
-    ev.new Scenario().testExpression("len({'a' : 1, 'b' : 2})", 2);
+    ev.new Scenario().testExpression("len({'a' : 1, 'b' : 2})", StarlarkInt.of(2));
   }
 
   @Test
diff --git a/src/test/java/net/starlark/java/eval/PrinterTest.java b/src/test/java/net/starlark/java/eval/PrinterTest.java
index 4af6b61..e5accf4 100644
--- a/src/test/java/net/starlark/java/eval/PrinterTest.java
+++ b/src/test/java/net/starlark/java/eval/PrinterTest.java
@@ -85,10 +85,15 @@
 
   @Test
   public void testFormatPositional() throws Exception {
-    assertThat(Starlark.formatWithList("%s %d", Tuple.of("foo", 3))).isEqualTo("foo 3");
-    assertThat(Starlark.format("%s %d", "foo", 3)).isEqualTo("foo 3");
+    assertThat(Starlark.formatWithList("%s %d", Tuple.of("foo", StarlarkInt.of(3))))
+        .isEqualTo("foo 3");
+    assertThat(Starlark.format("%s %d", "foo", StarlarkInt.of(3))).isEqualTo("foo 3");
 
-    assertThat(Starlark.format("%s %s %s", 1, null, 3)).isEqualTo("1 null 3");
+    // %d allows Integer or StarlarkInt
+    assertThat(Starlark.format("%d %d", StarlarkInt.of(123), 456)).isEqualTo("123 456");
+
+    assertThat(Starlark.format("%s %s %s", StarlarkInt.of(1), null, StarlarkInt.of(3)))
+        .isEqualTo("1 null 3");
 
     // Note: formatToString doesn't perform scalar x -> (x) conversion;
     // The %-operator is responsible for that.
@@ -102,11 +107,19 @@
         "%%s", "foo");
     checkFormatPositionalFails("unsupported format character \" \" at index 1 in \"% %s\"",
         "% %s", "foo");
-    assertThat(Starlark.format("%s", StarlarkList.of(null, 1, 2, 3))).isEqualTo("[1, 2, 3]");
-    assertThat(Starlark.format("%s", Tuple.of(1, 2, 3))).isEqualTo("(1, 2, 3)");
+    assertThat(
+            Starlark.format(
+                "%s",
+                StarlarkList.of(null, StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3))))
+        .isEqualTo("[1, 2, 3]");
+    assertThat(
+            Starlark.format(
+                "%s", Tuple.of(StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3))))
+        .isEqualTo("(1, 2, 3)");
     assertThat(Starlark.format("%s", StarlarkList.of(null))).isEqualTo("[]");
     assertThat(Starlark.format("%s", Tuple.of())).isEqualTo("()");
-    assertThat(Starlark.format("%% %d %r %s", 1, "2", "3")).isEqualTo("% 1 \"2\" 3");
+    assertThat(Starlark.format("%% %d %r %s", StarlarkInt.of(1), "2", "3"))
+        .isEqualTo("% 1 \"2\" 3");
 
     checkFormatPositionalFails(
         "invalid argument \"1\" for format pattern %d",
diff --git a/src/test/java/net/starlark/java/eval/ScriptTest.java b/src/test/java/net/starlark/java/eval/ScriptTest.java
index 8334c5b..0cce2e6 100644
--- a/src/test/java/net/starlark/java/eval/ScriptTest.java
+++ b/src/test/java/net/starlark/java/eval/ScriptTest.java
@@ -147,6 +147,10 @@
         } catch (SyntaxError.Exception ex) {
           // parser/resolver errors
           for (SyntaxError err : ex.errors()) {
+            // TODO(adonovan): don't allow expectations to match static errors;
+            // they should be a different test suite. It is dangerous to mix
+            // them in a chunk otherwise the presence of a static error causes
+            // the program not to run the dynamic assertions.
             if (!expected(expectations, err.message())) {
               System.err.println(err); // includes location
               ok = false;
diff --git a/src/test/java/net/starlark/java/eval/StarlarkEvaluationTest.java b/src/test/java/net/starlark/java/eval/StarlarkEvaluationTest.java
index 3fd4160..d8e121b 100644
--- a/src/test/java/net/starlark/java/eval/StarlarkEvaluationTest.java
+++ b/src/test/java/net/starlark/java/eval/StarlarkEvaluationTest.java
@@ -454,6 +454,24 @@
       throw new InternalError("buggy code");
     }
 
+    @StarlarkMethod(
+        name = "int_conversion",
+        doc = "test implicit StarlarkInt to Integer conversion",
+        parameters = {
+          @Param(name = "a", type = Integer.class),
+          @Param(
+              name = "b",
+              allowedTypes = {
+                @ParamType(type = String.class),
+                @ParamType(type = Integer.class),
+              }),
+          @Param(name = "c"),
+          @Param(name = "d"),
+        })
+    public String intConversion(Object a, Object b, Integer c, Object d) {
+      return String.format("(%s, %s, %s, %s)", a, b, c, d);
+    }
+
     @Override
     public String toString() {
       return "<mock>";
@@ -526,14 +544,14 @@
   public void testSimpleIf() throws Exception {
     ev.new Scenario()
         .setUp("def foo():", "  a = 0", "  x = 0", "  if x: a = 5", "  return a", "a = foo()")
-        .testLookup("a", 0);
+        .testLookup("a", StarlarkInt.of(0));
   }
 
   @Test
   public void testIfPass() throws Exception {
     ev.new Scenario()
         .setUp("def foo():", "  a = 1", "  x = True", "  if x: pass", "  return a", "a = foo()")
-        .testLookup("a", 1);
+        .testLookup("a", StarlarkInt.of(1));
   }
 
   @Test
@@ -558,7 +576,7 @@
             "    b = 3",
             "  return a + b",
             "x = " + fun + "()")
-        .testLookup("x", expected);
+        .testLookup("x", StarlarkInt.of(expected));
   }
 
   @Test
@@ -578,7 +596,7 @@
             "    else: a = 3",
             "  return a",
             "z = " + fun + "()")
-        .testLookup("z", expected);
+        .testLookup("z", StarlarkInt.of(expected));
   }
 
   @Test
@@ -609,7 +627,7 @@
             "  else:",
             "    return 3",
             "v = foo()")
-        .testLookup("v", v);
+        .testLookup("v", StarlarkInt.of(v));
   }
 
   @Test
@@ -848,7 +866,9 @@
         "    hit = 1",
         "  return [s, hit]",
         "x = foo()");
-    assertThat((Iterable<Object>) ev.lookup("x")).containsExactly(expected, 0).inOrder();
+    assertThat((Iterable<Object>) ev.lookup("x"))
+        .containsExactly(StarlarkInt.of(expected), StarlarkInt.of(0))
+        .inOrder();
   }
 
   @Test
@@ -873,7 +893,7 @@
         "       s = s + 1",
         "   return s",
         "x = foo()");
-    assertThat(ev.lookup("x")).isEqualTo(expected);
+    assertThat(ev.lookup("x")).isEqualTo(StarlarkInt.of(expected));
   }
 
   private void flowFromNestedBlocks(String statement, int expected) throws Exception {
@@ -888,7 +908,7 @@
         "       s = s + 1",
         "   return s",
         "y = foo2()");
-    assertThat(ev.lookup("y")).isEqualTo(expected);
+    assertThat(ev.lookup("y")).isEqualTo(StarlarkInt.of(expected));
   }
 
   @Test
@@ -924,7 +944,10 @@
         "   return [outer, first, second]",
         "x = foo()");
     assertThat((Iterable<Object>) ev.lookup("x"))
-        .containsExactly(outerExpected, firstExpected, secondExpected)
+        .containsExactly(
+            StarlarkInt.of(outerExpected),
+            StarlarkInt.of(firstExpected),
+            StarlarkInt.of(secondExpected))
         .inOrder();
   }
 
@@ -970,7 +993,7 @@
   public void testNoneAssignment() throws Exception {
     ev.new Scenario()
         .setUp("def foo(x=None):", "  x = 1", "  x = None", "  return 2", "s = foo()")
-        .testLookup("s", 2);
+        .testLookup("s", StarlarkInt.of(2));
   }
 
   @Test
@@ -1479,7 +1502,7 @@
   public void testAugmentedAssignment() throws Exception {
     ev.new Scenario()
         .setUp("def f1(x):", "  x += 1", "  return x", "", "foo = f1(41)")
-        .testLookup("foo", 42);
+        .testLookup("foo", StarlarkInt.of(42));
   }
 
   @Test
@@ -1495,7 +1518,7 @@
             "  return value",
             "",
             "f()[1] += 1") // `f()` should be called only once here
-        .testLookup("counter", StarlarkList.of(null, 1));
+        .testLookup("counter", StarlarkList.of(null, StarlarkInt.of(1)));
 
     // Check key position.
     ev.new Scenario()
@@ -1508,7 +1531,7 @@
             "  return 1",
             "",
             "value[f()] += 1") // `f()` should be called only once here
-        .testLookup("counter", StarlarkList.of(null, 1));
+        .testLookup("counter", StarlarkList.of(null, StarlarkInt.of(1)));
   }
 
   @Test
@@ -1607,7 +1630,12 @@
             "  t2 += (3, 4)",
             "  return t1, t2",
             "tuples = func()")
-        .testLookup("tuples", Tuple.of(Tuple.of(1, 2), Tuple.of(1, 2, 3, 4)));
+        .testLookup(
+            "tuples",
+            Tuple.of(
+                Tuple.of(StarlarkInt.of(1), StarlarkInt.of(2)),
+                Tuple.of(
+                    StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3), StarlarkInt.of(4))));
   }
 
   @Test
@@ -1627,7 +1655,7 @@
   public void testDictAssignmentAsLValue() throws Exception {
     ev.new Scenario()
         .setUp("def func():", "  d = {'a' : 1}", "  d['b'] = 2", "  return d", "d = func()")
-        .testLookup("d", ImmutableMap.of("a", 1, "b", 2));
+        .testLookup("d", ImmutableMap.of("a", StarlarkInt.of(1), "b", StarlarkInt.of(2)));
   }
 
   @Test
@@ -1640,7 +1668,9 @@
             "  e['d']['b'] = 2",
             "  return e",
             "e = func()")
-        .testLookup("e", ImmutableMap.of("d", ImmutableMap.of("a", 1, "b", 2)));
+        .testLookup(
+            "e",
+            ImmutableMap.of("d", ImmutableMap.of("a", StarlarkInt.of(1), "b", StarlarkInt.of(2))));
   }
 
   @Test
@@ -1674,42 +1704,47 @@
     ev.new Scenario()
         .setUp(
             "def func():", "  d = {'a' : 1}", "  d['b'], d['c'] = 2, 3", "  return d", "d = func()")
-        .testLookup("d", ImmutableMap.of("a", 1, "b", 2, "c", 3));
+        .testLookup(
+            "d",
+            ImmutableMap.of(
+                "a", StarlarkInt.of(1), "b", StarlarkInt.of(2), "c", StarlarkInt.of(3)));
   }
 
   @Test
   public void testDictItemPlusEqual() throws Exception {
     ev.new Scenario()
         .setUp("def func():", "  d = {'a' : 2}", "  d['a'] += 3", "  return d", "d = func()")
-        .testLookup("d", ImmutableMap.of("a", 5));
+        .testLookup("d", ImmutableMap.of("a", StarlarkInt.of(5)));
   }
 
   @Test
   public void testDictAssignmentAsLValueSideEffects() throws Exception {
     ev.new Scenario()
         .setUp("def func(d):", "  d['b'] = 2", "d = {'a' : 1}", "func(d)")
-        .testLookup("d", Dict.of((Mutability) null, "a", 1, "b", 2));
+        .testLookup(
+            "d", Dict.of((Mutability) null, "a", StarlarkInt.of(1), "b", StarlarkInt.of(2)));
   }
 
   @Test
   public void testAssignmentToListInDictSideEffect() throws Exception {
     ev.new Scenario()
         .setUp("l = [1, 2]", "d = {0: l}", "d[0].append(3)")
-        .testLookup("l", StarlarkList.of(null, 1, 2, 3));
+        .testLookup(
+            "l", StarlarkList.of(null, StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3)));
   }
 
   @Test
   public void testUserFunctionKeywordArgs() throws Exception {
     ev.new Scenario()
         .setUp("def foo(a, b, c):", "  return a + b + c", "s = foo(1, c=2, b=3)")
-        .testLookup("s", 6);
+        .testLookup("s", StarlarkInt.of(6));
   }
 
   @Test
   public void testFunctionCallOrdering() throws Exception {
     ev.new Scenario()
         .setUp("def func(): return foo() * 2", "def foo(): return 2", "x = func()")
-        .testLookup("x", 4);
+        .testLookup("x", StarlarkInt.of(4));
   }
 
   @Test
@@ -1733,7 +1768,7 @@
             "        else:",
             "            a = i",
             "res = beforeEven([1, 3, 4, 5])")
-        .testLookup("res", 3);
+        .testLookup("res", StarlarkInt.of(3));
   }
 
   @Test
@@ -1833,12 +1868,14 @@
 
   @Test
   public void testCannotCreateMixedListInStarlark() throws Exception {
-    ev.new Scenario().testExactOrder("['a', 'b', 1, 2]", "a", "b", 1, 2);
+    ev.new Scenario()
+        .testExactOrder("['a', 'b', 1, 2]", "a", "b", StarlarkInt.of(1), StarlarkInt.of(2));
   }
 
   @Test
   public void testCannotConcatListInStarlarkWithDifferentGenericTypes() throws Exception {
-    ev.new Scenario().testExactOrder("[1, 2] + ['a', 'b']", 1, 2, "a", "b");
+    ev.new Scenario()
+        .testExactOrder("[1, 2] + ['a', 'b']", StarlarkInt.of(1), StarlarkInt.of(2), "a", "b");
   }
 
   @Test
@@ -1853,13 +1890,14 @@
 
   @Test
   public void testSingletonTuple() throws Exception {
-    ev.new Scenario().testExactOrder("(1,)", 1);
+    ev.new Scenario().testExactOrder("(1,)", StarlarkInt.of(1));
   }
 
   @Test
   public void testDirFindsClassObjectFields() throws Exception {
     ev.new Scenario()
-        .update("s", new SimpleStruct(ImmutableMap.of("a", 1, "b", 2)))
+        .update(
+            "s", new SimpleStruct(ImmutableMap.of("a", StarlarkInt.of(1), "b", StarlarkInt.of(2))))
         .testExactOrder("dir(s)", "a", "b");
   }
 
@@ -1870,6 +1908,7 @@
         .testExactOrder(
             "dir(mock)",
             "function",
+            "int_conversion",
             "interrupted_struct_field",
             "is_empty",
             "nullfunc_failing",
@@ -1918,14 +1957,14 @@
 
   @Test
   public void testConditionalExpressionAtToplevel() throws Exception {
-    ev.new Scenario().setUp("x = 1 if 2 else 3").testLookup("x", 1);
+    ev.new Scenario().setUp("x = 1 if 2 else 3").testLookup("x", StarlarkInt.of(1));
   }
 
   @Test
   public void testConditionalExpressionInFunction() throws Exception {
     ev.new Scenario()
         .setUp("def foo(a, b, c): return a+b if c else a-b\n")
-        .testExpression("foo(23, 5, 0)", 18);
+        .testExpression("foo(23, 5, 0)", StarlarkInt.of(18));
   }
 
   // SimpleStructWithMethods augments SimpleStruct's fields with annotated Java methods.
@@ -2051,7 +2090,7 @@
         "  b = [a for a in range(3)]",
         "  return a",
         "x = foo()");
-    assertThat(ev.lookup("x")).isEqualTo(18);
+    assertThat(ev.lookup("x")).isEqualTo(StarlarkInt.of(18));
   }
 
   @Test
@@ -2062,7 +2101,7 @@
         "def f():",
         "  return x",
         "y = [f() for x in [2]][0]");
-    assertThat(ev.lookup("y")).isEqualTo(1);
+    assertThat(ev.lookup("y")).isEqualTo(StarlarkInt.of(1));
   }
 
   @Test
@@ -2083,4 +2122,23 @@
             () -> Starlark.addMethods(ImmutableMap.builder(), new Mock()));
     assertThat(ex).hasMessageThat().contains("method struct_field has structField=true");
   }
+
+  @Test
+  public void testIntegerReboxing() throws Exception {
+    ev.new Scenario()
+        .update("mock", new Mock())
+        .setUp("big = 111111111 * 111111111")
+        .testExpression("mock.int_conversion(1, 2, 3, 4)", "(1, 2, 3, 4)")
+        .testExpression("mock.int_conversion(1, 'b', 3, 'd')", "(1, b, 3, d)")
+        .testIfErrorContains(
+            "got 12345678987654321 for a, want value in signed 32-bit range",
+            "mock.int_conversion(big, 0, 0, 0)")
+        .testIfErrorContains(
+            "got 12345678987654321 for b, want value in signed 32-bit range",
+            "mock.int_conversion(0, big, 0, 0)")
+        .testIfErrorContains(
+            "got 12345678987654321 for c, want value in signed 32-bit range",
+            "mock.int_conversion(0, 0, big, 0)")
+        .testExpression("mock.int_conversion(0, 0, 0, big)", "(0, 0, 0, 12345678987654321)");
+  }
 }
diff --git a/src/test/java/net/starlark/java/eval/StarlarkListTest.java b/src/test/java/net/starlark/java/eval/StarlarkListTest.java
index 17b3fbc..dd75283 100644
--- a/src/test/java/net/starlark/java/eval/StarlarkListTest.java
+++ b/src/test/java/net/starlark/java/eval/StarlarkListTest.java
@@ -33,14 +33,14 @@
   @Test
   public void testIndex() throws Exception {
     ev.exec("l = [1, '2', 3]");
-    assertThat(ev.eval("l[0]")).isEqualTo(1);
+    assertThat(ev.eval("l[0]")).isEqualTo(StarlarkInt.of(1));
     assertThat(ev.eval("l[1]")).isEqualTo("2");
-    assertThat(ev.eval("l[2]")).isEqualTo(3);
+    assertThat(ev.eval("l[2]")).isEqualTo(StarlarkInt.of(3));
 
     ev.exec("t = (1, '2', 3)");
-    assertThat(ev.eval("t[0]")).isEqualTo(1);
+    assertThat(ev.eval("t[0]")).isEqualTo(StarlarkInt.of(1));
     assertThat(ev.eval("t[1]")).isEqualTo("2");
-    assertThat(ev.eval("t[2]")).isEqualTo(3);
+    assertThat(ev.eval("t[2]")).isEqualTo(StarlarkInt.of(3));
   }
 
   @Test
@@ -145,19 +145,25 @@
 
   @Test
   public void testListSize() throws Exception {
-    assertThat(ev.eval("len([42, 'hello, world', []])")).isEqualTo(3);
+    assertThat(ev.eval("len([42, 'hello, world', []])")).isEqualTo(StarlarkInt.of(3));
   }
 
   @Test
   public void testListEmpty() throws Exception {
-    assertThat(ev.eval("8 if [1, 2, 3] else 9")).isEqualTo(8);
-    assertThat(ev.eval("8 if [] else 9")).isEqualTo(9);
+    assertThat(ev.eval("8 if [1, 2, 3] else 9")).isEqualTo(StarlarkInt.of(8));
+    assertThat(ev.eval("8 if [] else 9")).isEqualTo(StarlarkInt.of(9));
   }
 
   @Test
   public void testListConcat() throws Exception {
     assertThat(ev.eval("[1, 2] + [3, 4]"))
-        .isEqualTo(StarlarkList.of(/*mutability=*/ null, 1, 2, 3, 4));
+        .isEqualTo(
+            StarlarkList.of(
+                /*mutability=*/ null,
+                StarlarkInt.of(1),
+                StarlarkInt.of(2),
+                StarlarkInt.of(3),
+                StarlarkInt.of(4)));
   }
 
   @Test
@@ -168,10 +174,10 @@
         "e1 = l[1]",
         "e2 = l[2]",
         "e3 = l[3]");
-    assertThat(ev.lookup("e0")).isEqualTo(1);
-    assertThat(ev.lookup("e1")).isEqualTo(2);
-    assertThat(ev.lookup("e2")).isEqualTo(3);
-    assertThat(ev.lookup("e3")).isEqualTo(4);
+    assertThat(ev.lookup("e0")).isEqualTo(StarlarkInt.of(1));
+    assertThat(ev.lookup("e1")).isEqualTo(StarlarkInt.of(2));
+    assertThat(ev.lookup("e2")).isEqualTo(StarlarkInt.of(3));
+    assertThat(ev.lookup("e3")).isEqualTo(StarlarkInt.of(4));
   }
 
   @Test
@@ -183,16 +189,16 @@
         "e2 = l[2]",
         "e3 = l[3]",
         "e4 = l[4]");
-    assertThat(ev.lookup("e0")).isEqualTo(1);
-    assertThat(ev.lookup("e1")).isEqualTo(2);
-    assertThat(ev.lookup("e2")).isEqualTo(3);
-    assertThat(ev.lookup("e3")).isEqualTo(4);
-    assertThat(ev.lookup("e4")).isEqualTo(5);
+    assertThat(ev.lookup("e0")).isEqualTo(StarlarkInt.of(1));
+    assertThat(ev.lookup("e1")).isEqualTo(StarlarkInt.of(2));
+    assertThat(ev.lookup("e2")).isEqualTo(StarlarkInt.of(3));
+    assertThat(ev.lookup("e3")).isEqualTo(StarlarkInt.of(4));
+    assertThat(ev.lookup("e4")).isEqualTo(StarlarkInt.of(5));
   }
 
   @Test
   public void testConcatListSize() throws Exception {
-    assertThat(ev.eval("len([1, 2] + [3, 4])")).isEqualTo(4);
+    assertThat(ev.eval("len([1, 2] + [3, 4])")).isEqualTo(StarlarkInt.of(4));
   }
 
   @Test
@@ -225,13 +231,13 @@
   @Test
   public void testConcatListNotEmpty() throws Exception {
     ev.exec("l = [1, 2] + [3, 4]", "v = 1 if l else 0");
-    assertThat(ev.lookup("v")).isEqualTo(1);
+    assertThat(ev.lookup("v")).isEqualTo(StarlarkInt.of(1));
   }
 
   @Test
   public void testConcatListEmpty() throws Exception {
     ev.exec("l = [] + []", "v = 1 if l else 0");
-    assertThat(ev.lookup("v")).isEqualTo(0);
+    assertThat(ev.lookup("v")).isEqualTo(StarlarkInt.of(0));
   }
 
   @Test
@@ -268,7 +274,9 @@
   @Test
   public void testMutatorsCheckMutability() throws Exception {
     Mutability mutability = Mutability.create("test");
-    StarlarkList<Object> list = StarlarkList.copyOf(mutability, ImmutableList.of(1, 2, 3));
+    StarlarkList<Object> list =
+        StarlarkList.copyOf(
+            mutability, ImmutableList.of(StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3)));
     mutability.freeze();
 
     // The casts force selection of the Starlark add/remove methods,
@@ -276,27 +284,37 @@
     // We could enable the List method, but then it would have to
     // report failures using unchecked exceptions.
     EvalException e =
-        assertThrows(EvalException.class, () -> list.add((Object) 4, (Location) null));
-    assertThat(e).hasMessageThat().isEqualTo("trying to mutate a frozen list value");
-    e = assertThrows(EvalException.class, () -> list.add(0, (Object) 4, (Location) null));
+        assertThrows(
+            EvalException.class, () -> list.add((Object) StarlarkInt.of(4), (Location) null));
     assertThat(e).hasMessageThat().isEqualTo("trying to mutate a frozen list value");
     e =
         assertThrows(
-            EvalException.class, () -> list.addAll(ImmutableList.of(4, 5, 6), (Location) null));
+            EvalException.class, () -> list.add(0, (Object) StarlarkInt.of(4), (Location) null));
+    assertThat(e).hasMessageThat().isEqualTo("trying to mutate a frozen list value");
+    e =
+        assertThrows(
+            EvalException.class,
+            () ->
+                list.addAll(
+                    ImmutableList.of(StarlarkInt.of(4), StarlarkInt.of(5), StarlarkInt.of(6)),
+                    (Location) null));
     assertThat(e).hasMessageThat().isEqualTo("trying to mutate a frozen list value");
     e = assertThrows(EvalException.class, () -> list.remove(0, (Location) null));
     assertThat(e).hasMessageThat().isEqualTo("trying to mutate a frozen list value");
-    e = assertThrows(EvalException.class, () -> list.set(0, 10, (Location) null));
+    e = assertThrows(EvalException.class, () -> list.set(0, StarlarkInt.of(10), (Location) null));
     assertThat(e).hasMessageThat().isEqualTo("trying to mutate a frozen list value");
   }
 
   @Test
   public void testCannotMutateAfterShallowFreeze() throws Exception {
     Mutability mutability = Mutability.createAllowingShallowFreeze("test");
-    StarlarkList<Object> list = StarlarkList.copyOf(mutability, ImmutableList.of(1, 2, 3));
+    StarlarkList<Object> list =
+        StarlarkList.copyOf(
+            mutability, ImmutableList.of(StarlarkInt.of(1), StarlarkInt.of(2), StarlarkInt.of(3)));
     list.unsafeShallowFreeze();
 
-    EvalException e = assertThrows(EvalException.class, () -> list.add((Object) 4, null));
+    EvalException e =
+        assertThrows(EvalException.class, () -> list.add((Object) StarlarkInt.of(4), null));
     assertThat(e).hasMessageThat().isEqualTo("trying to mutate a frozen list value");
   }
 
diff --git a/src/test/java/net/starlark/java/eval/StarlarkThreadDebuggingTest.java b/src/test/java/net/starlark/java/eval/StarlarkThreadDebuggingTest.java
index effffb3..473b8f5 100644
--- a/src/test/java/net/starlark/java/eval/StarlarkThreadDebuggingTest.java
+++ b/src/test/java/net/starlark/java/eval/StarlarkThreadDebuggingTest.java
@@ -213,11 +213,12 @@
 
   @Test
   public void testEvaluateVariableInScope() throws Exception {
-    Module module = Module.withPredeclared(StarlarkSemantics.DEFAULT, ImmutableMap.of("a", 1));
+    Module module =
+        Module.withPredeclared(StarlarkSemantics.DEFAULT, ImmutableMap.of("a", StarlarkInt.of(1)));
 
     StarlarkThread thread = newThread();
     Object a = Starlark.execFile(ParserInput.fromLines("a"), FileOptions.DEFAULT, module, thread);
-    assertThat(a).isEqualTo(1);
+    assertThat(a).isEqualTo(StarlarkInt.of(1));
   }
 
   @Test
@@ -247,6 +248,6 @@
         .isEqualTo(true);
     Starlark.execFile(ParserInput.fromLines("a = 1"), FileOptions.DEFAULT, module, thread);
     assertThat(Starlark.execFile(ParserInput.fromLines("a"), FileOptions.DEFAULT, module, thread))
-        .isEqualTo(1);
+        .isEqualTo(StarlarkInt.of(1));
   }
 }
diff --git a/src/test/java/net/starlark/java/eval/StarlarkThreadTest.java b/src/test/java/net/starlark/java/eval/StarlarkThreadTest.java
index 3afd37c..5bb6ebd 100644
--- a/src/test/java/net/starlark/java/eval/StarlarkThreadTest.java
+++ b/src/test/java/net/starlark/java/eval/StarlarkThreadTest.java
@@ -41,10 +41,10 @@
   @Test
   public void testDoubleUpdateSucceeds() throws Exception {
     assertThat(ev.lookup("VERSION")).isNull();
-    ev.update("VERSION", 42);
-    assertThat(ev.lookup("VERSION")).isEqualTo(42);
-    ev.update("VERSION", 43);
-    assertThat(ev.lookup("VERSION")).isEqualTo(43);
+    ev.update("VERSION", StarlarkInt.of(42));
+    assertThat(ev.lookup("VERSION")).isEqualTo(StarlarkInt.of(42));
+    ev.update("VERSION", StarlarkInt.of(43));
+    assertThat(ev.lookup("VERSION")).isEqualTo(StarlarkInt.of(43));
   }
 
   // Test assign through interpreter, ev.lookup through API:
@@ -87,7 +87,7 @@
       StarlarkThread thread = new StarlarkThread(mu, StarlarkSemantics.DEFAULT);
       Starlark.execFile(ParserInput.fromLines("True = 123"), FileOptions.DEFAULT, module, thread);
     }
-    assertThat(module.getGlobal("True")).isEqualTo(123);
+    assertThat(module.getGlobal("True")).isEqualTo(StarlarkInt.of(123));
   }
 
   @Test
diff --git a/src/test/java/net/starlark/java/eval/testdata/int.sky b/src/test/java/net/starlark/java/eval/testdata/int.sky
index 68d4877..e6b1680 100644
--- a/src/test/java/net/starlark/java/eval/testdata/int.sky
+++ b/src/test/java/net/starlark/java/eval/testdata/int.sky
@@ -7,10 +7,41 @@
 assert_eq(5 * 7, 35)
 assert_eq(5 - 7, -2)
 
+# big numbers (but no big literals yet)
+# TODO(adonovan): implement %x.
+assert_eq("%d" % (1 << 32), "4294967296") # ="0x100000000"
+assert_eq("%d" % (1 << 64), "18446744073709551616") # ="0x10000000000000000"
+assert_eq("%d" % (1 << 128), "340282366920938463463374607431768211456") # ="0x100000000000000000000000000000000"
+assert_eq("%d" % (-1 << 128), "-340282366920938463463374607431768211456") # ="-0x100000000000000000000000000000000"
+assert_eq((1 << 128) // (1 << 127), 2)
+
+# size boundaries
+maxint = (1<<31) - 1
+maxlong = (1<<63) - 1
+minint = -1 << 31
+minlong = -1 << 63
+assert_eq(str(maxint + 1), "2147483648")
+assert_eq(str(maxlong + 1), "9223372036854775808")
+assert_eq(str(minint - 1), "-2147483649")
+assert_eq(str(minlong - 1), "-9223372036854775809")
+
+# str(int)
+assert_eq(str(0), "0")
+assert_eq(str(1), "1")
+assert_eq(str(1<<24), "16777216")
+assert_eq(str(1<<48), "281474976710656")
+assert_eq(str(1<<96), "79228162514264337593543950336")
+assert_eq(str(-1<<24), "-16777216")
+assert_eq(str(-1<<48), "-281474976710656")
+assert_eq(str(-1<<96), "-79228162514264337593543950336")
+
 # truth
-assert_(123)
-assert_(-1)
 assert_(not 0)
+assert_(1)
+assert_(-1)
+assert_(1<<24) # int32
+assert_(1<<48) # int64
+assert_(1<<100) # big
 
 # comparisons
 assert_(5 > 2)
@@ -19,6 +50,31 @@
 assert_(not (2 + 1 > 3))
 assert_(2 + 2 <= 5)
 assert_(not (2 + 1 < 3))
+big = 1 << 100
+assert_(big == big)
+assert_(not (big != big))
+assert_(big != big + 1)
+assert_(not (big == big + 1))
+assert_(big - 1 < big and big < big + 1)
+assert_(-big - 1 < -big and -big < -big + 1)
+
+# multiplication
+assert_eq(1111 * 1111, 1234321)
+assert_eq(1111 * 1, 1111)
+assert_eq(1111 * -1, -1111)
+assert_eq(1111 * 0, 0)
+p1, p2 = 0x316c5239, 0x67c4a7d5 # 32-bit primes
+product = p1 * p2
+assert_eq(product, p2 * p1)
+assert_eq(product // p1, p2)
+assert_eq(product % p1, 0)
+assert_eq(maxint, 0x7fffffff)
+assert_eq(str(maxint * maxint), "4611686014132420609")
+assert_eq(str((1<<62) - (2<<31) + 1), "4611686014132420609")
+assert_eq(str(111111111 * 111111111), "12345678987654321")
+assert_eq(str(-(111111111 * 111111111)), "-12345678987654321")
+assert_eq(str((111111111 * 111111111) // 111111111), "111111111")
+assert_eq(str((111111111 * 111111111) // 111111111), "111111111")
 
 # division
 assert_eq(100 // 7, 14)
@@ -29,6 +85,12 @@
 assert_eq(98 // -7, -14)
 assert_eq(-98 // 7, -14)
 assert_eq(-98 // -7, 14)
+quot = 1169282 * 1000000 + 890553 # simplify when we have big literals
+assert_eq(product // 1234567, quot)
+assert_eq(product // -1234567, -quot-1)
+assert_eq(-product // 1234567, -quot-1)
+assert_eq(-product // -1234567, quot)
+assert_eq(((-1) << 31) // -1, 2147483647+1) # sole case of int // int that causes int overflow
 
 # remainder
 assert_eq(100 % 7, 2)
@@ -39,10 +101,17 @@
 assert_eq(98 % -7, 0)
 assert_eq(-98 % 7, 0)
 assert_eq(-98 % -7, 0)
+assert_eq( product %  1234567,  1013598)
+assert_eq( product % -1234567, -220969) # ditto
+assert_eq(-product % 1234567,   220969) # ditto
+assert_eq(-product % -1234567, -1013598)
 
 # precedence
 assert_eq(5 - 7 * 2 + 3, -6)
 assert_eq(4 * 5 // 2 + 5 // 2 * 4, 18)
+assert_eq(1<<8 - 1, 1 << (8-1)) # confusingly...
+assert_eq(8 | 3 ^ 4 & -2, 15)
+assert_eq(~8 >> 1 | 3 ^ 4 & -2 << 2 * 3 + 4 // -2, -5)
 
 # compound assignment
 def compound():
@@ -118,11 +187,14 @@
 assert_eq(0 >> 0, 0)
 assert_eq(1000 >> 100, 0)
 assert_eq(-10 >> 1000, -1)
+assert_eq(1 << 500 >> 499, 2)
+assert_eq(1 << 32, 0x10000 * 0x10000)
+assert_eq(1 << 64, 0x10000 * 0x10000 * 0x10000 * 0x10000)
 
-# precedence
-
-assert_eq(8 | 3 ^ 4 & -2, 15)
-assert_eq(~8 >> 1 | 3 ^ 4 & -2 << 2 * 3 + 4 // -2, -5)
+assert_eq(((0x1010 << 100) | (0x1100 << 100)) >> 100, 0x1110)
+assert_eq(((0x1010 << 100) ^ (0x1100 << 100)) >> 100, 0x0110)
+assert_eq(((0x1010 << 100) & (0x1100 << 100)) >> 100, 0x1000)
+assert_eq(~((~(0x1010 << 100)) >> 100), 0x1010)
 
 ---
 1 & False ### unsupported binary operation: int & bool
@@ -131,12 +203,8 @@
 ---
 ~False ### unsupported unary operation: ~bool
 ---
-1 << 31 ### integer overflow in left shift
----
-1 << 32 ### integer overflow in left shift
+1 << 520 ### shift count too large: 520
 ---
 1 << -4 ### negative shift count: -4
 ---
 2 >> -1 ### negative shift count: -1
----
-((-1) << 31) // -1 ### integer overflow in division
diff --git a/src/test/java/net/starlark/java/eval/testdata/int_constructor.sky b/src/test/java/net/starlark/java/eval/testdata/int_constructor.sky
index 17eb1f8..abd5861 100644
--- a/src/test/java/net/starlark/java/eval/testdata/int_constructor.sky
+++ b/src/test/java/net/starlark/java/eval/testdata/int_constructor.sky
@@ -1,9 +1,50 @@
-assert_eq(int('1'), 1)
-assert_eq(int('-1234'), -1234)
+# Tests of int(x, [base])
+
+# from number
+assert_eq(int(0), 0)
 assert_eq(int(42), 42)
 assert_eq(int(-1), -1)
+assert_eq(int(2147483647), 2147483647)
+# -2147483648 is not (yet) a valid int literal even though it's a
+# valid int value, hence the -1 expression.
+assert_eq(int(-2147483647 - 1), -2147483647 - 1)
+---
+2147483648 ### invalid base-10 integer constant: 2147483648
+---
+-2147483649 ### invalid base-10 integer constant: 2147483649
+---
+
+# from bool
 assert_eq(int(True), 1)
 assert_eq(int(False), 0)
+
+# from other
+int(None) ### parameter 'x' cannot be None
+---
+
+# from string
+int('') ### empty string
+---
+# no base
+assert_eq(int('0'), 0)
+assert_eq(int('1'), 1)
+assert_eq(int('42'), 42)
+assert_eq(int('-1'), -1)
+assert_eq(int('-1234'), -1234)
+assert_eq(int('2147483647'), 2147483647)
+assert_eq(int('-2147483648'), -2147483647 - 1)
+assert_eq(int('123456789012345678901234567891234567890'), int('123456789012345678901234567891234567891') - 1)
+assert_eq(int('-123456789012345678901234567891234567890'), int('-123456789012345678901234567891234567891') + 1)
+assert_eq(int('-0xabcdefabcdefabcdefabcdefabcdef', 0), int('-892059645479943313385225296292859375'))
+assert_eq(int('1111111111111', 2), 8191)
+assert_eq(int('1111111111111', 5), int('305175781'))
+assert_eq(int('1111111111111', 8), int('78536544841'))
+assert_eq(int('1111111111111', 10), int('1111111111111'))
+assert_eq(int('1111111111111', 16), int('300239975158033'))
+assert_eq(int('1111111111111', 36), int('4873763662273663093'))
+assert_eq(int('016'), 16) # zero ok when base != 0.
+assert_eq(int('+42'), 42) # '+' prefix ok
+# with base, no prefix
 assert_eq(int('11', 2), 3)
 assert_eq(int('11', 9), 10)
 assert_eq(int('AF', 16), 175)
@@ -11,34 +52,57 @@
 assert_eq(int('az', 36), 395)
 assert_eq(int('11', 10), 11)
 assert_eq(int('11', 0), 11)
+# base and prefix
 assert_eq(int('0b11', 0), 3)
 assert_eq(int('0B11', 2), 3)
 assert_eq(int('0o11', 0), 9)
 assert_eq(int('0O11', 8), 9)
 assert_eq(int('0XFF', 0), 255)
 assert_eq(int('0xFF', 16), 255)
-
+assert_eq(int('0b11', 0), 3)
+assert_eq(int('-0b11', 0), -3)
+assert_eq(int('+0b11', 0), 3)
+assert_eq(int('0B11', 2), 3)
+assert_eq(int('0o11', 0), 9)
+assert_eq(int('0O11', 8), 9)
+assert_eq(int('-11', 2), -3)
+assert_eq(int('016', 8), 14)
+assert_eq(int('016', 16), 22)
+assert_eq(int('0', 0), 0)
+int('0xFF', 8) ### invalid base-8 literal: "0xFF"
 ---
-int('1.5') ### invalid literal for int\(\) with base 10
+int('016', 0) ### cannot infer base when string begins with a 0: "016"
 ---
-int('ab') ### invalid literal for int\(\) with base 10: "ab"
+int('123', 3) ### invalid base-3 literal: "123"
 ---
-int(None) ### parameter 'x' cannot be None
+int('FF', 15) ### invalid base-15 literal: "FF"
 ---
-int('123', 3) ### invalid literal for int\(\) with base 3: "123"
+int('123', -1) ### invalid base -1 (want 2 <= base <= 36)
 ---
-int('FF', 15) ### invalid literal for int\(\) with base 15: "FF"
+int('123', 1) ### invalid base 1 \(want 2 <= base <= 36\)
 ---
-int('123', -1) ### int\(\) base must be >= 2 and <= 36
+int('123', 37) ### invalid base 37 \(want 2 <= base <= 36\)
 ---
-int('123', 1) ### int\(\) base must be >= 2 and <= 36
+int('123', 'x') ### parameter 'base' got value of type 'string', want 'int'
 ---
-int('123', 37) ### int\(\) base must be >= 2 and <= 36
+int(True, 2) ### can't convert non-string with explicit base
 ---
-int('0xFF', 8) ### invalid literal for int\(\) with base 8: "0xFF"
+int(True, 10) ### can't convert non-string with explicit base
 ---
-int(True, 2) ### int\(\) can't convert non-string with explicit base
+int(1, 2) ### can't convert non-string with explicit base
 ---
-int(1, 2) ### int\(\) can't convert non-string with explicit base
+# This case is allowed in Python but not Skylark
+int() ### missing 1 required positional argument: x
 ---
-int(True, 10) ### int\(\) can't convert non-string with explicit base
+# Surrounding whitespace is not allowed
+int('  42  ') ### invalid base-10 literal: "  42  "
+---
+int('-') ### invalid base-10 literal: "-"
+---
+int('+') ### invalid base-10 literal: "+"
+---
+int('0x') ### invalid base-10 literal: "0x"
+---
+int('1.5') ### invalid base-10 literal: "1.5"
+---
+int('ab') ### invalid base-10 literal: "ab"
diff --git a/src/test/java/net/starlark/java/eval/testdata/int_function.sky b/src/test/java/net/starlark/java/eval/testdata/int_function.sky
deleted file mode 100644
index ff72397..0000000
--- a/src/test/java/net/starlark/java/eval/testdata/int_function.sky
+++ /dev/null
@@ -1,95 +0,0 @@
-# int
-assert_eq(int(0), 0)
-assert_eq(int(42), 42)
-assert_eq(int(-1), -1)
-assert_eq(int(2147483647), 2147483647)
-# -2147483648 is not actually a valid int literal even though it's a
-# valid int value, hence the -1 expression.
-assert_eq(int(-2147483647 - 1), -2147483647 - 1)
-assert_eq(int(True), 1)
-assert_eq(int(False), 0)
-
----
-int(None) ### parameter 'x' cannot be None
----
-# This case is allowed in Python but not Skylark
-int() ### missing 1 required positional argument: x
----
-
-# string, no base
-# Includes same numbers as integer test cases above.
-assert_eq(int('0'), 0)
-assert_eq(int('42'), 42)
-assert_eq(int('-1'), -1)
-assert_eq(int('2147483647'), 2147483647)
-assert_eq(int('-2147483648'), -2147483647 - 1)
-# Leading zero allowed when not using base = 0.
-assert_eq(int('016'), 16)
-# Leading plus sign allowed for strings.
-assert_eq(int('+42'), 42)
-
----
-int(2147483648) ### invalid base-10 integer constant: 2147483648
----
-int(-2147483649) ### invalid base-10 integer constant: 2147483649
----
-int('') ### cannot be empty
----
-# Surrounding whitespace is not allowed
-int('  42  ') ### invalid literal for int() with base 10: "  42  "
----
-int('-') ### invalid literal for int() with base 10: "-"
----
-int('0x') ### invalid literal for int() with base 10: "0x"
----
-int('1.5') ### invalid literal for int() with base 10: "1.5"
----
-int('ab') ### invalid literal for int() with base 10: "ab"
----
-
-assert_eq(int('11', 2), 3)
-assert_eq(int('-11', 2), -3)
-assert_eq(int('11', 9), 10)
-assert_eq(int('AF', 16), 175)
-assert_eq(int('11', 36), 37)
-assert_eq(int('az', 36), 395)
-assert_eq(int('11', 10), 11)
-assert_eq(int('11', 0), 11)
-assert_eq(int('016', 8), 14)
-assert_eq(int('016', 16), 22)
-
----
-# invalid base
-int('016', 0) ### cannot infer base for int() when value begins with a 0: "016"
----
-int('123', 3) ### invalid literal for int() with base 3: "123"
----
-int('FF', 15) ### invalid literal for int() with base 15: "FF"
----
-int('123', -1) ### int() base must be >= 2 and <= 36
----
-int('123', 1) ### int() base must be >= 2 and <= 36
----
-int('123', 37) ### int() base must be >= 2 and <= 36
----
-int('123', 'x') ### base must be an integer (got 'string')
----
-
-# base with prefix
-assert_eq(int('0b11', 0), 3)
-assert_eq(int('-0b11', 0), -3)
-assert_eq(int('+0b11', 0), 3)
-assert_eq(int('0B11', 2), 3)
-assert_eq(int('0o11', 0), 9)
-assert_eq(int('0O11', 8), 9)
-assert_eq(int('0XFF', 0), 255)
-assert_eq(int('0xFF', 16), 255)
-
----
-int('0xFF', 8) ### invalid literal for int() with base 8: "0xFF"
----
-int(True, 2) ### int() can't convert non-string with explicit base
----
-int(1, 2) ### int() can't convert non-string with explicit base
----
-int(True, 10) ### int() can't convert non-string with explicit base