Introduce a new DexArrayType class that extends DexType

Each DexArrayType instance has direct access to its base and element types.

Bug: b/422947619
Change-Id: I3459225f2174adfeda78c404296592a37b7444bc
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
index 2094b4f..a6aa471 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiReferenceLevelCache.java
@@ -84,7 +84,7 @@
       if (reference.isDexMethod() && reference.asDexMethod().match(factory.objectMembers.clone)) {
         return appView.computedMinApiLevel();
       }
-      return lookup(contextType.toBaseType(factory), unknownValue, ignoringDesugaredLibrary);
+      return lookup(contextType.getBaseType(), unknownValue, ignoringDesugaredLibrary);
     }
     if (contextType.isPrimitiveType() || contextType.isVoidType()) {
       return appView.computedMinApiLevel();
diff --git a/src/main/java/com/android/tools/r8/assistant/InstrumentedReflectiveMethodList.java b/src/main/java/com/android/tools/r8/assistant/InstrumentedReflectiveMethodList.java
index 854b0ce..60939dc 100644
--- a/src/main/java/com/android/tools/r8/assistant/InstrumentedReflectiveMethodList.java
+++ b/src/main/java/com/android/tools/r8/assistant/InstrumentedReflectiveMethodList.java
@@ -64,7 +64,7 @@
     builder.put(
         factory.createMethod(
             factory.classType,
-            factory.createProto(factory.createArrayType(1, factory.methodType)),
+            factory.createProto(factory.methodType.toArrayType(factory)),
             "getDeclaredMethods"),
         getMethodReferenceWithClassParameter("onClassGetDeclaredMethods"));
     builder.put(
@@ -73,7 +73,7 @@
     builder.put(
         factory.createMethod(
             factory.classType,
-            factory.createProto(factory.createArrayType(1, factory.fieldType)),
+            factory.createProto(factory.fieldType.toArrayType(factory)),
             "getDeclaredFields"),
         getMethodReferenceWithClassParameter("onClassGetDeclaredFields"));
     builder.put(
@@ -83,7 +83,7 @@
     builder.put(
         factory.createMethod(
             factory.classType,
-            factory.createProto(factory.createArrayType(1, factory.constructorType)),
+            factory.createProto(factory.constructorType.toArrayType(factory)),
             "getDeclaredConstructors"),
         getMethodReferenceWithClassParameter("onClassGetDeclaredConstructors"));
     builder.put(
@@ -93,7 +93,7 @@
     builder.put(
         factory.createMethod(
             factory.classType,
-            factory.createProto(factory.createArrayType(1, factory.methodType)),
+            factory.createProto(factory.methodType.toArrayType(factory)),
             "getMethods"),
         getMethodReferenceWithClassParameter("onClassGetMethods"));
     builder.put(
@@ -103,7 +103,7 @@
     builder.put(
         factory.createMethod(
             factory.classType,
-            factory.createProto(factory.createArrayType(1, factory.fieldType)),
+            factory.createProto(factory.fieldType.toArrayType(factory)),
             "getFields"),
         getMethodReferenceWithClassParameter("onClassGetFields"));
     builder.put(
@@ -113,7 +113,7 @@
     builder.put(
         factory.createMethod(
             factory.classType,
-            factory.createProto(factory.createArrayType(1, factory.constructorType)),
+            factory.createProto(factory.constructorType.toArrayType(factory)),
             "getConstructors"),
         getMethodReferenceWithClassParameter("onClassGetConstructors"));
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index c7f5181..51ceedc 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -81,7 +81,7 @@
     assert array.type.isObject();
     ValueType memberType = ValueType.fromMemberType(getType());
     if (array.preciseType != null) {
-      value = state.push(array.preciseType.toArrayElementType(builder.appView.dexItemFactory()));
+      value = state.push(array.preciseType.getArrayElementType());
       assert state.peek().type == memberType;
     } else {
       value = state.push(memberType);
@@ -119,7 +119,7 @@
                     config,
                     head.asInitializedNonNullReferenceTypeWithoutInterfaces()
                         .getInitializedType()
-                        .toArrayElementType(dexItemFactory));
+                        .getArrayElementType());
               }
             });
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java b/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
index d1ec598..4627ef5 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
@@ -106,8 +106,7 @@
     }
     if (target.isArrayType()) {
       return source.isArrayType()
-          && isAssignable(
-              source.toArrayElementType(dexItemFactory), target.toArrayElementType(dexItemFactory));
+          && isAssignable(source.getArrayElementType(), target.getArrayElementType());
     }
     assert target.isClassType();
     if (source.isArrayType()) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
index 1d89b7d..e2e46fa 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstClass.java
@@ -136,13 +136,10 @@
 
   private String getInternalName(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     DexType rewrittenType = graphLens.lookupType(type, codeLens);
-    switch (rewrittenType.toShorty()) {
-      case '[':
-      case 'L':
-        return namingLens.lookupInternalName(rewrittenType);
-      default:
-        throw new Unreachable("Unexpected type in const-class: " + rewrittenType);
+    if (rewrittenType.isReferenceType()) {
+      return namingLens.lookupInternalName(rewrittenType);
     }
+    throw new Unreachable("Unexpected type in const-class: " + rewrittenType);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
index e7e9d7d..0b4679a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfNewArray.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCompareHelper;
+import com.android.tools.r8.graph.DexArrayType;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
@@ -31,11 +32,11 @@
 
 public class CfNewArray extends CfInstruction implements CfTypeInstruction {
 
-  private final DexType type;
+  private final DexArrayType type;
 
   public CfNewArray(DexType type) {
     assert type.isArrayType();
-    this.type = type;
+    this.type = type.asArrayType();
   }
 
   @Override
@@ -49,7 +50,7 @@
   }
 
   @Override
-  public DexType getType() {
+  public DexArrayType getType() {
     return type;
   }
 
@@ -98,19 +99,13 @@
   }
 
   private String getElementInternalName(
-      DexItemFactory dexItemFactory,
       GraphLens graphLens,
       GraphLens codeLens,
       NamingLens namingLens) {
     assert !type.isPrimitiveArrayType();
-    StringBuilder renamedElementDescriptor = new StringBuilder();
-    // Intentionally starting from 1 to get the element descriptor.
-    int numberOfLeadingSquareBrackets = getType().getNumberOfLeadingSquareBrackets();
-    for (int i = 1; i < numberOfLeadingSquareBrackets; i++) {
-      renamedElementDescriptor.append("[");
-    }
-    DexType baseType = getType().toBaseType(dexItemFactory);
-    DexType rewrittenBaseType = graphLens.lookupType(baseType, codeLens);
+    StringBuilder renamedElementDescriptor =
+        new StringBuilder("[".repeat(type.getArrayElementType().getArrayTypeDimensions()));
+    DexType rewrittenBaseType = graphLens.lookupType(type.getBaseType(), codeLens);
     renamedElementDescriptor.append(
         namingLens.lookupDescriptor(rewrittenBaseType).toSourceString());
     return DescriptorUtils.descriptorToInternalName(renamedElementDescriptor.toString());
@@ -131,8 +126,7 @@
       visitor.visitIntInsn(Opcodes.NEWARRAY, getPrimitiveTypeCode());
     } else {
       visitor.visitTypeInsn(
-          Opcodes.ANEWARRAY,
-          getElementInternalName(dexItemFactory, graphLens, codeLens, namingLens));
+          Opcodes.ANEWARRAY, getElementInternalName(graphLens, codeLens, namingLens));
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithoutInterfaces.java b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithoutInterfaces.java
index 7d09140..f48d6fa 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithoutInterfaces.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedNonNullReferenceFrameTypeWithoutInterfaces.java
@@ -53,10 +53,9 @@
   }
 
   @Override
-  @SuppressWarnings("ReferenceEquality")
   public Object getTypeOpcode(GraphLens graphLens, GraphLens codeLens, NamingLens namingLens) {
     DexType rewrittenType = graphLens.lookupType(type, codeLens);
-    assert rewrittenType != DexItemFactory.nullValueType;
+    assert rewrittenType.isNotIdenticalTo(DexItemFactory.nullValueType);
     switch (rewrittenType.toShorty()) {
       case 'L':
         return namingLens.lookupInternalName(rewrittenType);
diff --git a/src/main/java/com/android/tools/r8/graph/DexArrayType.java b/src/main/java/com/android/tools/r8/graph/DexArrayType.java
new file mode 100644
index 0000000..046543f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/DexArrayType.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2025, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.errors.Unreachable;
+
+public class DexArrayType extends DexType {
+
+  private final DexType baseType;
+  private final DexType elementType;
+  private final int dimensions;
+
+  public DexArrayType(DexString descriptor, DexType elementType) {
+    super(descriptor);
+    this.baseType = elementType.getBaseType();
+    this.elementType = elementType;
+    this.dimensions = getArrayTypeDimensions(descriptor);
+    assert dimensions > 0;
+    assert baseType.isIdenticalTo(elementType) || dimensions > 1;
+  }
+
+  @Override
+  public DexType getArrayElementType() {
+    return elementType;
+  }
+
+  public DexType getArrayElementTypeAfterDimension(int dimensionsToRemove) {
+    assert dimensions >= dimensionsToRemove;
+    DexType current = this;
+    while (dimensionsToRemove > 0) {
+      current = current.asArrayType().getArrayElementType();
+      dimensionsToRemove--;
+    }
+    return current;
+  }
+
+  @Override
+  public int getArrayTypeDimensions() {
+    return dimensions;
+  }
+
+  private static int getArrayTypeDimensions(DexString descriptor) {
+    int leadingSquareBrackets = 0;
+    while (descriptor.content[leadingSquareBrackets] == '[') {
+      leadingSquareBrackets++;
+    }
+    return leadingSquareBrackets;
+  }
+
+  @Override
+  public DexType getBaseType() {
+    return baseType;
+  }
+
+  public int getElementSizeForPrimitiveArrayType() {
+    assert isPrimitiveArrayType();
+    switch (baseType.getDescriptor().getFirstByteAsChar()) {
+      case 'Z': // boolean
+      case 'B': // byte
+        return 1;
+      case 'S': // short
+      case 'C': // char
+        return 2;
+      case 'I': // int
+      case 'F': // float
+        return 4;
+      case 'J': // long
+      case 'D': // double
+        return 8;
+      default:
+        throw new Unreachable("Not array of primitives '" + descriptor + "'");
+    }
+  }
+
+  @Override
+  public boolean isArrayType() {
+    return true;
+  }
+
+  @Override
+  public boolean isPrimitiveArrayType() {
+    return elementType.isPrimitiveType();
+  }
+
+  @Override
+  public DexArrayType asArrayType() {
+    return this;
+  }
+
+  public DexType replaceBaseType(DexType newBase, DexItemFactory dexItemFactory) {
+    return newBase.isIdenticalTo(baseType)
+        ? this
+        : newBase.toArrayType(dexItemFactory, getArrayTypeDimensions());
+  }
+
+  @Override
+  public char toShorty() {
+    return 'L';
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 60cdebb..ad567ab 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -653,7 +653,7 @@
   public final DexType kotlinEnumEntriesList =
       createStaticallyKnownType("Lkotlin/enums/EnumEntriesList;");
   public final DexMethod kotlinEnumEntriesListInit =
-      createInstanceInitializer(kotlinEnumEntriesList, createArrayType(1, enumType));
+      createInstanceInitializer(kotlinEnumEntriesList, enumType.toArrayType(this));
 
   public final DexType javaIoFileType = createStaticallyKnownType("Ljava/io/File;");
   public final DexType javaMathBigIntegerType = createStaticallyKnownType("Ljava/math/BigInteger;");
@@ -1949,7 +1949,7 @@
                 typeDescriptorType,
                 classType,
                 stringType,
-                createArrayType(1, methodHandleType)),
+                methodHandleType.toArrayType(DexItemFactory.this)),
             "bootstrap");
   }
 
@@ -2453,7 +2453,7 @@
       FieldAccessFlags accessFlags = staticField.getAccessFlags();
       assert accessFlags.isStatic();
       return staticField.getType().isArrayType()
-          && staticField.getType().toArrayElementType(DexItemFactory.this) == enumType
+          && staticField.getType().getArrayElementType() == enumType
           && accessFlags.isSynthetic()
           && accessFlags.isFinal();
     }
@@ -3389,7 +3389,7 @@
 
   private void addPossiblySynthesizedType(DexType type) {
     if (type.isArrayType()) {
-      type = type.toBaseType(this);
+      type = type.getBaseType();
     }
     if (type.isClassType()) {
       possibleCompilerSynthesizedTypes.add(type);
@@ -3410,7 +3410,15 @@
     if (committed != null) {
       return committed;
     }
-    return types.computeIfAbsent(descriptor, DexType::new);
+    if (descriptor.getFirstByteAsChar() != '[') {
+      return types.computeIfAbsent(descriptor, DexType::new);
+    }
+    DexType pending = types.get(descriptor);
+    if (pending != null) {
+      return pending;
+    }
+    DexType elementType = createType(descriptor.toArrayElementDescriptor(this));
+    return types.computeIfAbsent(descriptor, d -> new DexArrayType(d, elementType));
   }
 
   public DexType createType(String descriptor) {
@@ -3429,11 +3437,6 @@
     return types.get(descriptor);
   }
 
-  public DexType createArrayType(int nesting, DexType baseType) {
-    assert nesting > 0;
-    return createType("[".repeat(nesting) + baseType.toDescriptorString());
-  }
-
   public DexField createField(DexType clazz, DexType type, DexString name) {
     DexField field = new DexField(clazz, type, name, skipNameValidationForTesting);
     return canonicalize(committedFields, fields, field);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java
index 88e91d8..ef52d0f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -69,7 +69,7 @@
   public abstract Iterable<DexType> getReferencedTypes();
 
   public Iterable<DexType> getReferencedBaseTypes(DexItemFactory dexItemFactory) {
-    return Iterables.transform(getReferencedTypes(), type -> type.toBaseType(dexItemFactory));
+    return Iterables.transform(getReferencedTypes(), type -> type.getBaseType());
   }
 
   public boolean verifyReferencedBaseTypesMatches(
diff --git a/src/main/java/com/android/tools/r8/graph/DexProto.java b/src/main/java/com/android/tools/r8/graph/DexProto.java
index d9e1cfd..6263483 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProto.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProto.java
@@ -75,11 +75,11 @@
   }
 
   public Iterable<DexType> getParameterBaseTypes(DexItemFactory dexItemFactory) {
-    return Iterables.transform(parameters, type -> type.toBaseType(dexItemFactory));
+    return Iterables.transform(parameters, type -> type.getBaseType());
   }
 
   public Iterable<DexType> getBaseTypes(DexItemFactory dexItemFactory) {
-    return Iterables.transform(getTypes(), type -> type.toBaseType(dexItemFactory));
+    return Iterables.transform(getTypes(), type -> type.getBaseType());
   }
 
   public Iterable<DexType> getTypes() {
@@ -150,7 +150,7 @@
   public String createShortyString() {
     StringBuilder shortyBuilder = new StringBuilder();
     shortyBuilder.append(returnType.toShorty());
-    for (DexType argumentType : parameters.values) {
+    for (DexType argumentType : parameters) {
       shortyBuilder.append(argumentType.toShorty());
     }
     return shortyBuilder.toString();
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index 620027f..aa0355a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -691,4 +691,12 @@
     System.arraycopy(content, 0, newContent, dimensions, content.length);
     return dexItemFactory.createString(javaLangStringLength + dimensions, newContent);
   }
+
+  public DexString toArrayElementDescriptor(DexItemFactory dexItemFactory) {
+    assert getFirstByteAsChar() == '[';
+    int newContentLength = content.length - 1;
+    byte[] newContent = new byte[newContentLength];
+    System.arraycopy(content, 1, newContent, 0, newContentLength);
+    return dexItemFactory.createString(javaLangStringLength - 1, newContent);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 234295e..150179a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -22,7 +22,6 @@
 import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.google.common.collect.ImmutableList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
 import java.util.function.BiConsumer;
@@ -49,7 +48,7 @@
 
   // Bundletool is merging classes that may originate from a build with an old version of R8.
   // Allow merging of classes that use names from older versions of R8.
-  private static List<String> OLD_SYNTHESIZED_NAMES =
+  private static final List<String> OLD_SYNTHESIZED_NAMES =
       ImmutableList.of(
           "$r8$backportedMethods$utility",
           "$r8$java8methods$utility",
@@ -65,7 +64,7 @@
   private String toStringCache = null;
 
   DexType(DexString descriptor) {
-    assert !descriptor.toString().contains(".") : "Malformed descriptor: " + descriptor.toString();
+    assert !descriptor.toString().contains(".") : "Malformed descriptor: " + descriptor;
     this.descriptor = descriptor;
   }
 
@@ -283,8 +282,7 @@
   }
 
   public char toShorty() {
-    char c = descriptor.getFirstByteAsChar();
-    return c == '[' ? 'L' : c;
+    return descriptor.getFirstByteAsChar();
   }
 
   @Override
@@ -377,8 +375,16 @@
   }
 
   public boolean isArrayType() {
-    char firstChar = descriptor.getFirstByteAsChar();
-    return firstChar == '[';
+    return false;
+  }
+
+  public boolean isPrimitiveArrayType() {
+    assert !isArrayType();
+    return false;
+  }
+
+  public DexArrayType asArrayType() {
+    return null;
   }
 
   public boolean isClassType() {
@@ -392,13 +398,6 @@
     return isReferenceType;
   }
 
-  public boolean isPrimitiveArrayType() {
-    if (!isArrayType()) {
-      return false;
-    }
-    return DescriptorUtils.isPrimitiveType((char) descriptor.content[1]);
-  }
-
   public boolean isWideType() {
     return isDoubleType() || isLongType();
   }
@@ -430,63 +429,11 @@
     return asProgramClassOrNull(definitions.definitionFor(this));
   }
 
-  public boolean isProgramType(DexDefinitionSupplier definitions) {
-    DexClass clazz = definitions.definitionFor(this);
-    return clazz != null && clazz.isProgramClass();
-  }
-
   public boolean isResolvable(AppView<?> appView) {
     DexClass clazz = appView.definitionFor(this);
     return clazz != null && clazz.isResolvable(appView);
   }
 
-  public int elementSizeForPrimitiveArrayType() {
-    assert isPrimitiveArrayType();
-    switch (descriptor.content[1]) {
-      case 'Z': // boolean
-      case 'B': // byte
-        return 1;
-      case 'S': // short
-      case 'C': // char
-        return 2;
-      case 'I': // int
-      case 'F': // float
-        return 4;
-      case 'J': // long
-      case 'D': // double
-        return 8;
-      default:
-        throw new Unreachable("Not array of primitives '" + descriptor + "'");
-    }
-  }
-
-  public int getNumberOfLeadingSquareBrackets() {
-    int leadingSquareBrackets = 0;
-    while (descriptor.content[leadingSquareBrackets] == '[') {
-      leadingSquareBrackets++;
-    }
-    return leadingSquareBrackets;
-  }
-
-  public DexType toBaseType(DexItemFactory dexItemFactory) {
-    int leadingSquareBrackets = getNumberOfLeadingSquareBrackets();
-    if (leadingSquareBrackets == 0) {
-      return this;
-    }
-    DexString newDesc =
-        dexItemFactory.createString(
-            descriptor.length() - leadingSquareBrackets,
-            Arrays.copyOfRange(
-                descriptor.content, leadingSquareBrackets, descriptor.content.length));
-    return dexItemFactory.createType(newDesc);
-  }
-
-  public DexType replaceBaseType(DexType newBase, DexItemFactory dexItemFactory) {
-    assert this.isArrayType();
-    assert !newBase.isArrayType();
-    return newBase.toArrayType(getNumberOfLeadingSquareBrackets(), dexItemFactory);
-  }
-
   public DexType replacePackage(String newPackageDescriptor, DexItemFactory dexItemFactory) {
     assert isClassType();
     String descriptorString = toDescriptorString();
@@ -514,32 +461,6 @@
     return addSuffix("$" + index, dexItemFactory);
   }
 
-  public DexType toArrayType(int dimensions, DexItemFactory dexItemFactory) {
-    return dexItemFactory.createType(descriptor.toArrayDescriptor(dimensions, dexItemFactory));
-  }
-
-  public int getArrayTypeDimensions() {
-    for (int i = 0; i < descriptor.content.length; i++) {
-      if (descriptor.content[i] != '[') {
-        return i;
-      }
-    }
-    return 0;
-  }
-
-  public DexType toArrayElementType(DexItemFactory dexItemFactory) {
-    return toArrayElementAfterDimension(1, dexItemFactory);
-  }
-
-  public DexType toArrayElementAfterDimension(int dimension, DexItemFactory dexItemFactory) {
-    assert getArrayTypeDimensions() >= dimension;
-    DexString newDesc =
-        dexItemFactory.createString(
-            descriptor.length() - dimension,
-            Arrays.copyOfRange(descriptor.content, dimension, descriptor.content.length));
-    return dexItemFactory.createType(newDesc);
-  }
-
   private String getPackageOrName(boolean packagePart) {
     assert isClassType();
     String descriptor = toDescriptorString();
@@ -584,4 +505,26 @@
   public String getPackageName() {
     return DescriptorUtils.getPackageNameFromBinaryName(toBinaryName());
   }
+
+  // Array methods.
+
+  public DexType getArrayElementType() {
+    throw new Unreachable();
+  }
+
+  public int getArrayTypeDimensions() {
+    return 0;
+  }
+
+  public DexType getBaseType() {
+    return this;
+  }
+
+  public DexType toArrayType(DexItemFactory factory) {
+    return toArrayType(factory, 1);
+  }
+
+  public DexType toArrayType(DexItemFactory factory, int dimensions) {
+    return factory.createType(descriptor.toArrayDescriptor(dimensions, factory));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java b/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
index a9cd08c..55353ce 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeUtils.java
@@ -42,7 +42,7 @@
     if (type.isArrayType()) {
       ArrayTypeElement arrayType = type.asArrayType();
       DexType baseType = toDexType(factory, arrayType.getBaseType());
-      return baseType.toArrayType(arrayType.getNesting(), factory);
+      return baseType.toArrayType(factory, arrayType.getNesting());
     }
     assert type.isClassType();
     return type.asClassType().toDexType(factory);
@@ -50,8 +50,7 @@
 
   public static DexType findApiSafeUpperBound(
       AppView<? extends AppInfoWithClassHierarchy> appView, DexType type) {
-    DexItemFactory factory = appView.dexItemFactory();
-    DexType baseType = type.toBaseType(factory);
+    DexType baseType = type.getBaseType();
     if (baseType.isPrimitiveType()) {
       return type;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 4b5f81b..e625d5a 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -815,8 +815,7 @@
           addInstruction(new CfConstNumber(operand, ValueType.INT));
           break;
         case Opcodes.NEWARRAY:
-          addInstruction(
-              new CfNewArray(factory.createArrayType(1, arrayTypeDesc(operand, factory))));
+          addInstruction(new CfNewArray(arrayTypeDesc(operand, factory).toArrayType(factory)));
           break;
         default:
           throw new Unreachable("Unexpected int opcode " + opcode);
@@ -898,7 +897,7 @@
           addInstruction(cfNew);
           break;
         case Opcodes.ANEWARRAY:
-          addInstruction(new CfNewArray(factory.createArrayType(1, type)));
+          addInstruction(new CfNewArray(type.toArrayType(factory)));
           break;
         case Opcodes.CHECKCAST:
           addInstruction(new CfCheckCast(type));
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
index 14a560b..c5bba10 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectToOffsetMapping.java
@@ -297,7 +297,7 @@
       StartupProfile startupProfile) {
     startupProfile.forEachRule(
         rule -> {
-          DexType type = rule.getReference().getContextType().toBaseType(dexItemFactory());
+          DexType type = rule.getReference().getContextType().getBaseType();
           if (type.isPrimitiveType()) {
             assert false;
             return;
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java b/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java
index 35031ff..5a5c6e0 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.FieldResolutionResult.SingleFieldResolutionResult;
@@ -37,13 +36,11 @@
 public class GetArrayOfMissingTypeVerifyErrorWorkaround
     implements TraceFieldAccessEnqueuerAnalysis {
 
-  private final DexItemFactory dexItemFactory;
   private final Enqueuer enqueuer;
   private final AndroidApiLevelCompute apiLevelCompute;
 
   public GetArrayOfMissingTypeVerifyErrorWorkaround(
       AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
-    this.dexItemFactory = appView.dexItemFactory();
     this.enqueuer = enqueuer;
     this.apiLevelCompute = appView.apiLevelCompute();
   }
@@ -91,7 +88,7 @@
     if (!fieldType.isArrayType()) {
       return false;
     }
-    DexType baseType = fieldType.toBaseType(dexItemFactory);
+    DexType baseType = fieldType.getBaseType();
     if (!baseType.isClassType()) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java
index 78a001b..42c6e3d 100644
--- a/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.graph.fixup;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexArrayType;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -360,15 +361,12 @@
   }
 
   /** Fixup a type reference. */
-  @SuppressWarnings("ReferenceEquality")
   public DexType fixupType(DexType type) {
     if (type.isArrayType()) {
-      DexType base = type.toBaseType(dexItemFactory);
+      DexArrayType arrayType = type.asArrayType();
+      DexType base = arrayType.getBaseType();
       DexType fixed = fixupType(base);
-      if (base == fixed) {
-        return type;
-      }
-      return type.replaceBaseType(fixed, dexItemFactory);
+      return arrayType.replaceBaseType(fixed, dexItemFactory);
     }
     if (type.isClassType()) {
       return mapClassType(type);
diff --git a/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java
index 0b1bc8b..5308fc4 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/NonIdentityGraphLens.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.graph.lens;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexArrayType;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -111,9 +112,10 @@
       return lookupClassType(type, appliedLens);
     }
     if (type.isArrayType()) {
-      DexType baseType = type.toBaseType(dexItemFactory);
+      DexArrayType arrayType = type.asArrayType();
+      DexType baseType = type.getBaseType();
       DexType newType = lookupType(baseType, appliedLens);
-      return baseType.isIdenticalTo(newType) ? type : type.replaceBaseType(newType, dexItemFactory);
+      return arrayType.replaceBaseType(newType, dexItemFactory);
     }
     assert type.isNullValueType() || type.isPrimitiveType() || type.isVoidType();
     return type;
@@ -165,11 +167,10 @@
 
   public final DexType getNextType(DexType type) {
     if (type.isArrayType()) {
-      DexType baseType = type.toBaseType(dexItemFactory());
+      DexArrayType arrayType = type.asArrayType();
+      DexType baseType = arrayType.getBaseType();
       DexType newBaseType = getNextClassType(baseType);
-      if (newBaseType.isNotIdenticalTo(baseType)) {
-        return type.replaceBaseType(newBaseType, dexItemFactory());
-      }
+      return arrayType.replaceBaseType(newBaseType, dexItemFactory());
     } else if (type.isClassType()) {
       return getNextClassType(type);
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
index 892c40c..64cd182 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
@@ -68,7 +68,7 @@
     // If any field has a non-public type, then field merging may lead to check-cast instructions
     // being synthesized. These synthesized accesses depends on the package.
     for (DexEncodedField field : clazz.fields()) {
-      DexType fieldBaseType = field.getType().toBaseType(appView.dexItemFactory());
+      DexType fieldBaseType = field.getType().getBaseType();
       if (fieldBaseType.isClassType()) {
         DexClass fieldBaseClass = appView.definitionFor(fieldBaseType);
         if (fieldBaseClass == null || !fieldBaseClass.isPublic()) {
@@ -93,7 +93,7 @@
                           // current class in its package in case we end up synthesizing a
                           // check-cast for the field type after relaxing the type of the field
                           // after instance field merging.
-                          DexType fieldBaseType = field.getType().toBaseType(dexItemFactory());
+                          DexType fieldBaseType = field.getType().getBaseType();
                           if (fieldBaseType.isClassType()) {
                             DexClass fieldBaseClass = appView.definitionFor(fieldBaseType);
                             if (fieldBaseClass == null || !fieldBaseClass.isPublic()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 15b3293..d06a558 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -250,7 +250,7 @@
     assert newArrayEmpty != null || newArrayFilled != null;
 
     DexType arrayType = newArrayEmpty != null ? newArrayEmpty.type : newArrayFilled.getArrayType();
-    if (arrayType.toBaseType(appView.dexItemFactory()) != context.getHolder().type) {
+    if (arrayType.getBaseType() != context.getHolder().type) {
       return null;
     }
     if (value.hasDebugUsers() || value.hasPhiUsers()) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
index 8a3686a..a4cdd26 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
@@ -43,7 +43,7 @@
       assert baseTypeLattice.isClassType();
       baseType = baseTypeLattice.asClassType().getClassType();
     }
-    return factory.createArrayType(getNesting(), baseType);
+    return baseType.toArrayType(factory, getNesting());
   }
 
   public int getNesting() {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
index b9b357b..94bcb93 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
@@ -414,9 +414,7 @@
 
   static ArrayTypeElement objectArrayType(AppView<?> appView, Nullability nullability) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
-    return fromDexType(
-            dexItemFactory.createArrayType(1, dexItemFactory.objectType), nullability, appView)
-        .asArrayType();
+    return fromDexType(dexItemFactory.objectArrayType, nullability, appView).asArrayType();
   }
 
   public static ClassTypeElement stringClassType(AppView<?> appView) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElementFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElementFactory.java
index 29690b0..0e7635b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElementFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElementFactory.java
@@ -58,10 +58,7 @@
       }
       memberType =
           TypeElement.fromDexType(
-              type.toArrayElementType(appView.dexItemFactory()),
-              Nullability.maybeNull(),
-              appView,
-              true);
+              type.getArrayElementType(), Nullability.maybeNull(), appView, true);
     }
     TypeElement finalMemberType = memberType;
     return referenceTypes
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
index 34e3007..169089e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
@@ -98,7 +98,7 @@
   @Override
   boolean internalIsMaterializableInContext(
       AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
-    DexType baseType = type.toBaseType(appView.dexItemFactory());
+    DexType baseType = type.getBaseType();
     if (baseType.isClassType()) {
       DexClass clazz = appView.definitionFor(baseType);
       return clazz != null
@@ -111,7 +111,7 @@
 
   @Override
   public boolean isMaterializableInAllContexts(AppView<? extends AppInfoWithLiveness> appView) {
-    DexType baseType = type.toBaseType(appView.dexItemFactory());
+    DexType baseType = type.getBaseType();
     if (baseType.isPrimitiveType()) {
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 5afa942..f54dbe1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -176,7 +176,7 @@
       // JVM 8 §4.10.1.9.aaload: Array component type of null is null.
       return arrayType;
     }
-    return arrayType.toArrayElementType(appView.dexItemFactory());
+    return arrayType.getArrayElementType();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 1b1d55c..966b09d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -62,7 +62,7 @@
       // elimination may lead to verification errors. See b/123269162.
       if (options.canHaveArtCheckCastVerifierBug()
           && getType().isArrayType()
-          && getType().toBaseType(options.dexItemFactory()).isFloatType()) {
+          && getType().getBaseType().isFloatType()) {
         return true;
       }
       return false;
@@ -155,7 +155,7 @@
       return true;
     }
     AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-    DexType baseType = type.toBaseType(appView.dexItemFactory());
+    DexType baseType = type.getBaseType();
     if (baseType.isClassType()) {
       DexClass definition = appView.definitionFor(baseType);
       // Check that the class and its super types are present.
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 5273534..ec8bce9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -119,7 +119,7 @@
       ProgramMethod context,
       AbstractValueSupplier abstractValueSupplier,
       SideEffectAssumption assumption) {
-    DexType baseType = getType().toBaseType(appView.dexItemFactory());
+    DexType baseType = getType().getBaseType();
     if (baseType.isPrimitiveType()) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index 0b914a2..826654c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -136,7 +136,7 @@
       // However, in this case the original code is invalid, since it does not verify.
       return true;
     }
-    DexType baseType = type.toBaseType(appView.dexItemFactory());
+    DexType baseType = type.getBaseType();
     if (baseType.isIdenticalTo(context.getHolderType()) || baseType.isPrimitiveType()) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index bb1400e..8c9355b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -128,7 +128,7 @@
       ProgramMethod context,
       AbstractValueSupplier abstractValueSupplier,
       SideEffectAssumption assumption) {
-    DexType baseType = type.isArrayType() ? type.toBaseType(appView.dexItemFactory()) : type;
+    DexType baseType = type.isArrayType() ? type.getBaseType() : type;
     if (baseType.isPrimitiveType()) {
       // Primitives types are known to be present and accessible.
       assert !type.isWideType() : "The array's contents must be single-word";
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index de0aa46..95a5f4f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -93,7 +93,7 @@
   }
 
   private boolean isArrayTypeInaccessible(AppView<?> appView, ProgramMethod context) {
-    DexType baseType = type.toBaseType(appView.dexItemFactory());
+    DexType baseType = type.getBaseType();
     return !DexTypeUtils.isTypeAccessibleInMethodContext(appView, baseType, context);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilled.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilled.java
index 01855e3..26a31ad 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilled.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilled.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexArrayType;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexType;
@@ -32,11 +33,11 @@
 
 public class NewArrayFilled extends Invoke {
 
-  private final DexType type;
+  private final DexArrayType type;
 
   public NewArrayFilled(DexType type, Value result, List<Value> arguments) {
     super(result, arguments);
-    this.type = type;
+    this.type = type.asArrayType();
   }
 
   public static Builder builder() {
@@ -58,7 +59,7 @@
     return getArrayType();
   }
 
-  public DexType getArrayType() {
+  public DexArrayType getArrayType() {
     return type;
   }
 
@@ -184,7 +185,7 @@
       ProgramMethod context,
       AbstractValueSupplier abstractValueSupplier,
       SideEffectAssumption assumption) {
-    DexType baseType = type.isArrayType() ? type.toBaseType(appView.dexItemFactory()) : type;
+    DexType baseType = type.isArrayType() ? type.getBaseType() : type;
     if (baseType.isPrimitiveType()) {
       // Primitives types are known to be present and accessible.
       assert !type.isWideType() : "The array's contents must be single-word";
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index aef1339..b293b33 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -39,6 +39,7 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
+import com.android.tools.r8.graph.DexArrayType;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndField;
@@ -387,12 +388,13 @@
               DexMethod invokedMethod = invoke.getInvokedMethod();
               DexType invokedHolder = invokedMethod.holder;
               if (invokedHolder.isArrayType()) {
-                DexType baseType = invokedHolder.toBaseType(factory);
+                DexArrayType arrayType = invokedHolder.asArrayType();
+                DexType baseType = arrayType.getBaseType();
                 new InstructionReplacer(code, current, iterator, affectedPhis)
                     .replaceInstructionIfTypeChanged(
                         baseType,
                         (t, v) -> {
-                          DexType mappedHolder = invokedHolder.replaceBaseType(t, factory);
+                          DexType mappedHolder = arrayType.replaceBaseType(t, factory);
                           // Just reuse proto and name, as no methods on array types cant be renamed
                           // nor change signature.
                           DexMethod actualTarget =
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
index a674ff5..907dd14 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ArrayConstructionSimplifier.java
@@ -167,7 +167,7 @@
     //   desired interface, then we could add check-cast instructions for arguments we're not sure
     //   about.
     NewArrayEmpty newArrayEmpty = arrayValues.getDefinition().asNewArrayEmpty();
-    DexType elementType = newArrayEmpty.type.toArrayElementType(dexItemFactory);
+    DexType elementType = newArrayEmpty.type.getArrayElementType();
     boolean needsTypeCheck =
         !elementType.isPrimitiveType() && elementType.isNotIdenticalTo(dexItemFactory.objectType);
     if (!needsTypeCheck) {
@@ -180,7 +180,7 @@
     // !instruction.instructionMayHaveSideEffects(). Alternatively, we could replace the
     // new-array-empty with a const-class instruction in this case.
     if (!DexTypeUtils.isTypeAccessibleInMethodContext(
-        appView, elementType.toBaseType(dexItemFactory), code.context())) {
+        appView, elementType.getBaseType(), code.context())) {
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/ClassGetNameOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/ClassGetNameOptimizer.java
index 9941ea0..d612470 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/ClassGetNameOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/ClassGetNameOptimizer.java
@@ -101,8 +101,8 @@
 
       ConstClass constClass = in.definition.asConstClass();
       DexType type = constClass.getType();
-      int arrayDepth = type.getNumberOfLeadingSquareBrackets();
-      DexType baseType = type.toBaseType(dexItemFactory);
+      int arrayDepth = type.getArrayTypeDimensions();
+      DexType baseType = type.getBaseType();
       // Make sure base type is a class type.
       if (!baseType.isClassType()) {
         continue;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
index 1b8ab2e..d2cf4fb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/FilledNewArrayRewriter.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexArrayType;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
@@ -252,7 +253,7 @@
         return false;
       }
       // filled-new-array is implemented only for int[] and Object[].
-      DexType arrayType = newArrayFilled.getArrayType();
+      DexArrayType arrayType = newArrayFilled.getArrayType();
       if (arrayType.isIdenticalTo(dexItemFactory.intArrayType)) {
         // For int[], using filled-new-array is usually smaller than filled-array-data.
         // filled-new-array supports up to 5 registers before it's filled-new-array/range.
@@ -278,7 +279,7 @@
           return false;
         }
         if (!rewriteArrayOptions.canUseFilledNewArrayOfArrays()
-            && arrayType.getNumberOfLeadingSquareBrackets() > 1) {
+            && arrayType.getArrayTypeDimensions() > 1) {
           return false;
         }
         // Check that all arguments to the array is the array type or that the array is type
@@ -286,7 +287,7 @@
         if (rewriteArrayOptions.canHaveSubTypesInFilledNewArrayBug()
             && arrayType.isNotIdenticalTo(dexItemFactory.objectArrayType)
             && !arrayType.isPrimitiveArrayType()) {
-          DexType arrayElementType = arrayType.toArrayElementType(dexItemFactory);
+          DexType arrayElementType = arrayType.getArrayElementType();
           for (Value elementValue : newArrayFilled.inValues()) {
             if (!canStoreElementInNewArrayFilled(elementValue.getType(), arrayElementType)) {
               return false;
@@ -311,11 +312,11 @@
         }
         ArrayTypeElement arrayTypeElement = valueType.asArrayType();
         if (arrayTypeElement == null
-            || arrayTypeElement.getNesting() != elementType.getNumberOfLeadingSquareBrackets()) {
+            || arrayTypeElement.getNesting() != elementType.getArrayTypeDimensions()) {
           return false;
         }
         valueType = arrayTypeElement.getBaseType();
-        elementType = elementType.toBaseType(dexItemFactory);
+        elementType = elementType.getBaseType();
       }
       assert !valueType.isArrayType();
       assert !elementType.isArrayType();
@@ -387,7 +388,7 @@
       NewArrayFilledData newArrayFilledData =
           new NewArrayFilledData(
               newArrayFilled.outValue(),
-              newArrayFilled.getArrayType().elementSizeForPrimitiveArrayType(),
+              newArrayFilled.getArrayType().getElementSizeForPrimitiveArrayType(),
               newArrayFilled.size(),
               contents);
       newArrayFilledData.setPosition(newArrayFilled.getPosition());
@@ -401,7 +402,7 @@
     }
 
     private short[] computeArrayFilledData(NewArrayFilled newArrayFilled) {
-      int elementSize = newArrayFilled.getArrayType().elementSizeForPrimitiveArrayType();
+      int elementSize = newArrayFilled.getArrayType().getElementSizeForPrimitiveArrayType();
       int size = newArrayFilled.size();
       if (elementSize == 1) {
         short[] result = new short[(size + 1) / 2];
@@ -554,7 +555,7 @@
       instructionIterator.add(constNumber);
 
       // Add the ArrayPut instruction.
-      DexType arrayElementType = newArrayEmpty.getArrayType().toArrayElementType(dexItemFactory);
+      DexType arrayElementType = newArrayEmpty.getArrayType().getArrayElementType();
       MemberType memberType = MemberType.fromDexType(arrayElementType);
       ArrayPut arrayPut =
           ArrayPut.create(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
index 01ffc31..2dccf5c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/TrivialCheckCastAndInstanceOfRemover.java
@@ -173,7 +173,7 @@
     Value inValue = checkCast.object();
     Value outValue = checkCast.outValue();
     DexType castType = checkCast.getType();
-    DexType baseCastType = castType.toBaseType(dexItemFactory);
+    DexType baseCastType = castType.getBaseType();
 
     // If the cast type is not accessible in the current context, we should not remove the cast
     // in order to preserve runtime errors. Note that JVM and ART behave differently: see
@@ -198,7 +198,7 @@
     if (options.canHaveArtCheckCastVerifierBug()) {
       if (inValue.getType().isNullType()
           && castType.isArrayType()
-          && castType.toBaseType(dexItemFactory).isFloatType()) {
+          && castType.getBaseType().isFloatType()) {
         return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
       }
     }
@@ -207,7 +207,7 @@
     // See b/132420510 and b/223424356.
     if (options.canHaveIncorrectJoinForArrayOfInterfacesBug()) {
       if (castType.isArrayType()) {
-        DexType baseType = castType.toBaseType(dexItemFactory);
+        DexType baseType = castType.getBaseType();
         if (baseType.isClassType() && baseType.isInterface(appViewWithLiveness)) {
           return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
         }
@@ -326,7 +326,7 @@
 
     // If the instance-of type is not accessible in the current context, we should not remove the
     // instance-of instruction in order to preserve IllegalAccessError.
-    DexType instanceOfBaseType = instanceOf.type().toBaseType(dexItemFactory);
+    DexType instanceOfBaseType = instanceOf.type().getBaseType();
     if (instanceOfBaseType.isClassType()) {
       DexClass instanceOfClass = appView.definitionFor(instanceOfBaseType);
       if (instanceOfClass == null
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 053fb0a..6b4f4e0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -1313,7 +1313,7 @@
                 (ignore, methodArg) ->
                     CollectionMethodGenerators.generateMapOf(factory, methodArg, formalCount)));
       }
-      proto = factory.createProto(type, factory.createArrayType(1, factory.mapEntryType));
+      proto = factory.createProto(type, factory.mapEntryType.toArrayType(factory));
       method = factory.createMethod(type, proto, "ofEntries");
       addProvider(
           new MethodGenerator(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index 4d0f768..2032108 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -513,8 +513,8 @@
     if (subType.isArrayType()) {
       if (superType.isArrayType()) {
         // X[] -> Y[].
-        return isSameOrDerived(factory,
-            subType.toArrayElementType(factory), superType.toArrayElementType(factory));
+        return isSameOrDerived(
+            factory, subType.getArrayElementType(), superType.getArrayElementType());
       }
       return superType.isIdenticalTo(factory.objectType); // T[] -> Object.
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
index e37fd11..f41b8c6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
@@ -53,7 +53,7 @@
     builder.add(
         tryCatchStart,
         new CfConstNumber(classes.size(), ValueType.INT),
-        new CfNewArray(factory.createArrayType(1, serviceType)));
+        new CfNewArray(serviceType.toArrayType(factory)));
 
     for (int i = 0; i < classes.size(); i++) {
       builder.add(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodGenerators.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodGenerators.java
index 5041d87..b1493b6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodGenerators.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodGenerators.java
@@ -65,7 +65,7 @@
 
   @SuppressWarnings("BadImport")
   public static CfCode generateMapOf(DexItemFactory factory, DexMethod method, int formalCount) {
-    DexType mapEntryArray = factory.createArrayType(1, factory.mapEntryType);
+    DexType mapEntryArray = factory.mapEntryType.toArrayType(factory);
     DexType simpleEntry = factory.abstractMapSimpleEntryType;
     DexMethod simpleEntryConstructor =
         factory.createMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryTypeRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryTypeRewriter.java
index 6f0909d..4b6ac62 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/DesugaredLibraryTypeRewriter.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.desugar.desugaredlibrary;
 
+import com.android.tools.r8.graph.DexArrayType;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
@@ -54,8 +55,11 @@
     @Override
     public DexType rewrittenType(DexType type) {
       if (type.isArrayType()) {
-        DexType rewrittenBaseType = rewrittenType(type.toBaseType(factory));
-        return rewrittenBaseType != null ? type.replaceBaseType(rewrittenBaseType, factory) : null;
+        DexArrayType arrayType = type.asArrayType();
+        DexType rewrittenBaseType = rewrittenType(arrayType.getBaseType());
+        return rewrittenBaseType != null
+            ? arrayType.replaceBaseType(rewrittenBaseType, factory)
+            : null;
       }
       return rewriteType.get(type);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
index 35802f8..b6d9d25 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -152,8 +152,7 @@
       DexType type, DexMethod apiGenericTypesConversion, DexMethod method, ProgramMethod context) {
     if (type.isArrayType()) {
       assert apiGenericTypesConversion == null;
-      return shouldConvert(
-          type.toBaseType(appView.dexItemFactory()), apiGenericTypesConversion, method, context);
+      return shouldConvert(type.getBaseType(), apiGenericTypesConversion, method, context);
     }
     if (apiGenericTypesConversion != null) {
       return true;
@@ -240,7 +239,7 @@
       Supplier<UniqueContext> contextSupplier) {
     DexMethod conversion =
         ensureConversionMethod(
-            type.toArrayElementType(factory),
+            type.getArrayElementType(),
             srcType == type,
             null,
             eventConsumer,
@@ -260,7 +259,7 @@
       Supplier<UniqueContext> contextSupplier) {
     DexMethod conversion =
         getExistingProgramConversionMethod(
-            type.toArrayElementType(factory),
+            type.getArrayElementType(),
             srcType == type,
             null,
             eventConsumer,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
index f15da9b..9a1b90f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.java
@@ -40,7 +40,6 @@
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItem;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.ApiLevelRange;
@@ -58,21 +57,16 @@
 
   static final int MACHINE_VERSION_NUMBER = 200;
 
-  private final DexItemFactory factory;
   private final Map<String, String> packageMap = new TreeMap<>();
   private static final String chars =
       "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789æÆøØ";
   private int next = 0;
 
-  public MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter(DexItemFactory factory) {
-    this.factory = factory;
-  }
+  public MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter() {}
 
   public static void export(
-      MultiAPILevelMachineDesugaredLibrarySpecification specification,
-      StringConsumer output,
-      DexItemFactory factory) {
-    new MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter(factory)
+      MultiAPILevelMachineDesugaredLibrarySpecification specification, StringConsumer output) {
+    new MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter()
         .internalExport(specification, output);
   }
 
@@ -280,12 +274,8 @@
       return type.toString();
     }
     if (type.isArrayType()) {
-      StringBuilder sb = new StringBuilder();
-      sb.append(typeToString(type.toBaseType(factory)));
-      for (int i = 0; i < type.getNumberOfLeadingSquareBrackets(); i++) {
-        sb.append("[]");
-      }
-      return sb.toString();
+      return typeToString(type.getBaseType())
+          + "[]".repeat(Math.max(0, type.getArrayTypeDimensions()));
     }
     String pack =
         packageMap.computeIfAbsent(type.getPackageName(), k -> nextMinifiedPackagePrefix());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/DesugaredLibraryConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/DesugaredLibraryConverter.java
index d4efa45..63bb88b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/DesugaredLibraryConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/DesugaredLibraryConverter.java
@@ -72,7 +72,7 @@
         getAppForConversion(options, libraryFiles, desugaredLibraryFiles);
     MultiAPILevelHumanDesugaredLibrarySpecification humanSpec =
         getInputAsHumanSpecification(options, jsonResource, jsonConfig, appForConversion);
-    String outputString = convertToMachineSpecification(options, appForConversion, humanSpec);
+    String outputString = convertToMachineSpecification(appForConversion, humanSpec);
 
     Files.write(output, Collections.singleton(outputString));
 
@@ -80,17 +80,14 @@
   }
 
   private static String convertToMachineSpecification(
-      InternalOptions options,
-      DexApplication appForConversion,
-      MultiAPILevelHumanDesugaredLibrarySpecification humanSpec)
-      throws IOException {
+      DexApplication appForConversion, MultiAPILevelHumanDesugaredLibrarySpecification humanSpec) {
     HumanToMachineSpecificationConverter converter =
         new HumanToMachineSpecificationConverter(Timing.empty());
     MultiAPILevelMachineDesugaredLibrarySpecification machineSpec =
         converter.convertAllAPILevels(humanSpec, appForConversion);
     Box<String> machineJson = new Box<>();
     MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.export(
-        machineSpec, (string, handler) -> machineJson.set(string), options.dexItemFactory());
+        machineSpec, (string, handler) -> machineJson.set(string));
     return machineJson.get();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java
index c81a156..e87aa41 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java
@@ -94,8 +94,7 @@
   private DesugarDescription rewriteTwrGetSuppressedInvoke() {
     DexItemFactory factory = appView.dexItemFactory();
     DexProto proto =
-        factory.createProto(
-            factory.createArrayType(1, factory.throwableType), factory.throwableType);
+        factory.createProto(factory.throwableType.toArrayType(factory), factory.throwableType);
     return DesugarDescription.builder()
         .setDesugarRewrite(
             (position,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java
index 281856c..7aebc9c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java
@@ -115,9 +115,7 @@
               builder.getType(), factory.booleanArrayType, factory.createString("enumCacheSet"));
       enumCacheField =
           factory.createField(
-              builder.getType(),
-              factory.createArrayType(1, enumType),
-              factory.createString("enumCache"));
+              builder.getType(), enumType.toArrayType(factory), factory.createString("enumCache"));
       enumEqMethods.put(
           enumType,
           generateEnumEqMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchIRRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchIRRewriter.java
index 3727b03..25c865c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchIRRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchIRRewriter.java
@@ -184,7 +184,7 @@
             newValue =
                 code.createValue(
                     TypeElement.fromDexType(
-                        fieldAccessor.getFirst().getType().toBaseType(appView.dexItemFactory()),
+                        fieldAccessor.getFirst().getType().getBaseType(),
                         Nullability.maybeNull(),
                         appView));
             Value arrayValue =
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
index 52499aa..16e71b8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
@@ -466,7 +466,7 @@
     }
     DexType ct1ElementType = null;
     if (ct1Type.isArrayType()) {
-      ct1ElementType = ct1Type.toArrayElementType(factory);
+      ct1ElementType = ct1Type.getArrayElementType();
       if (ct1ElementType != factory.intType
           && ct1ElementType != factory.longType
           && !ct1ElementType.isReferenceType()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 1d6f319..c557d2f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -385,7 +385,7 @@
     public static ConstraintWithTarget classIsVisible(
         ProgramMethod context, DexType clazz, AppView<?> appView) {
       if (clazz.isArrayType()) {
-        return classIsVisible(context, clazz.toArrayElementType(appView.dexItemFactory()), appView);
+        return classIsVisible(context, clazz.getArrayElementType(), appView);
       }
 
       if (clazz.isPrimitiveType()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index f542d90..afe5593 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -172,7 +172,7 @@
         inType.isClassType()
             ? inType.asClassType().getClassType()
             : inType.asArrayType().toDexType(dexItemFactory);
-    DexType baseType = type.toBaseType(dexItemFactory);
+    DexType baseType = type.getBaseType();
     // Make sure base type is a class type.
     if (!baseType.isClassType()) {
       return;
@@ -253,7 +253,7 @@
       return;
     }
     // Make sure the (base) type is resolvable.
-    DexType baseType = type.toBaseType(dexItemFactory);
+    DexType baseType = type.getBaseType();
     DexClass baseClass = appView.appInfo().definitionForWithoutExistenceAssert(baseType);
     if (baseClass == null || !baseClass.isResolvable(appView)) {
       return;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index b76765a..905267d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -233,7 +233,7 @@
 
   private DexProgramClass getEnumUnboxingCandidateOrNull(DexType type) {
     if (type.isArrayType()) {
-      return getEnumUnboxingCandidateOrNull(type.toBaseType(appView.dexItemFactory()));
+      return getEnumUnboxingCandidateOrNull(type.getBaseType());
     }
     if (type.isPrimitiveType() || type.isVoidType()) {
       return null;
@@ -428,8 +428,7 @@
     // type and cannot be dealt with.
     // If the cast is on a dynamically typed object, the checkCast can be simply removed.
     // This allows enum array clone and valueOf to work correctly.
-    DexProgramClass enumClass =
-        getEnumUnboxingCandidateOrNull(checkCast.getType().toBaseType(factory));
+    DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(checkCast.getType().getBaseType());
     if (enumClass == null) {
       return;
     }
@@ -441,7 +440,7 @@
   }
 
   private void analyzeInstanceOf(InstanceOf instanceOf) {
-    DexType baseType = instanceOf.type().toBaseType(factory);
+    DexType baseType = instanceOf.type().getBaseType();
     DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(baseType);
     if (enumClass != null) {
       markEnumAsUnboxable(Reason.INSTANCE_OF, enumClass);
@@ -595,8 +594,7 @@
         }
       } else if (use.isNewArrayFilled()) {
         DexProgramClass enumClass =
-            getEnumUnboxingCandidateOrNull(
-                use.asNewArrayFilled().getArrayType().toBaseType(factory));
+            getEnumUnboxingCandidateOrNull(use.asNewArrayFilled().getArrayType().getBaseType());
         if (enumClass != null) {
           eligibleEnums.add(enumClass.getType());
         }
@@ -1300,7 +1298,7 @@
     }
     // The put value has to be of the field type.
     if (!enumUnboxingCandidatesInfo.isAssignableTo(
-        field.getReference().type.toBaseType(factory), enumClass.type)) {
+        field.getReference().type.getBaseType(), enumClass.type)) {
       return Reason.TYPE_MISMATCH_FIELD_PUT;
     }
     return Reason.ELIGIBLE;
@@ -1431,7 +1429,7 @@
       for (int i = 0; i < mostAccurateTarget.getParameters().size(); i++) {
         if (invoke.getArgumentForParameter(i) == enumValue
             && !enumUnboxingCandidatesInfo.isAssignableTo(
-                mostAccurateTarget.getParameter(i).toBaseType(factory), enumClass.getType())) {
+                mostAccurateTarget.getParameter(i).getBaseType(), enumClass.getType())) {
           return new IllegalInvokeWithImpreciseParameterTypeReason(
               mostAccurateTarget.getReference());
         }
@@ -1619,7 +1617,7 @@
   private Reason analyzeReturnUser(ProgramMethod context, DexProgramClass enumClass) {
     DexType returnType = context.getReturnType();
     if (returnType.isNotIdenticalTo(enumClass.type)
-        && returnType.toBaseType(factory).isNotIdenticalTo(enumClass.type)) {
+        && returnType.getBaseType().isNotIdenticalTo(enumClass.type)) {
       return Reason.IMPLICIT_UP_CAST_IN_RETURN;
     }
     return Reason.ELIGIBLE;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 8021284..bea3e50 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -137,7 +137,7 @@
     for (DexEncodedMethod method : clazz.virtualMethods()) {
       assert method.getParameters().isEmpty()
           || appView.options().testing.allowInjectedAnnotationMethods;
-      DexType valueType = method.returnType().toBaseType(appView.dexItemFactory());
+      DexType valueType = method.returnType().getBaseType();
       if (enumToUnboxCandidates.isCandidate(valueType)) {
         if (!enumUnboxer.reportFailure(valueType, Reason.ANNOTATION)) {
           enumToUnboxCandidates.removeCandidate(valueType);
@@ -167,7 +167,7 @@
 
   @SuppressWarnings("ReferenceEquality")
   private void removePinnedIfNotHolder(DexMember<?, ?> member, DexType type) {
-    DexType baseType = type.toBaseType(factory);
+    DexType baseType = type.getBaseType();
     if (baseType != member.holder) {
       removePinnedCandidate(baseType);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index e114635..7cca071 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -112,8 +112,7 @@
       assert next.isArgument();
       if (argumentInfo.isRewrittenTypeInfo()) {
         RewrittenTypeInfo rewrittenTypeInfo = argumentInfo.asRewrittenTypeInfo();
-        DexType enumType =
-            getEnumClassTypeOrNull(rewrittenTypeInfo.getOldType().toBaseType(factory));
+        DexType enumType = getEnumClassTypeOrNull(rewrittenTypeInfo.getOldType().getBaseType());
         if (rewrittenTypeInfo.hasSingleValue()
             && rewrittenTypeInfo.getSingleValue().isSingleNumberValue()) {
           assert rewrittenTypeInfo
@@ -557,7 +556,7 @@
       IRCode code,
       Map<Instruction, DexType> convertedEnums,
       InstructionListIterator instructionIterator) {
-    DexType arrayBaseType = newArrayFilled.getArrayType().toBaseType(factory);
+    DexType arrayBaseType = newArrayFilled.getArrayType().getBaseType();
     if (isArrayUsedOnlyForHashCode(newArrayFilled, factory)) {
       DexType rewrittenArrayType =
           newArrayFilled.getArrayType().replaceBaseType(factory.objectType, factory);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index 8d65820..a6bda7d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCodeWithLens;
+import com.android.tools.r8.graph.DexArrayType;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedField.Builder;
@@ -1083,15 +1084,12 @@
     return factory.createProto(returnType, arguments);
   }
 
-  @SuppressWarnings("ReferenceEquality")
   private DexType fixupType(DexType type) {
     if (type.isArrayType()) {
-      DexType base = type.toBaseType(factory);
+      DexArrayType arrayType = type.asArrayType();
+      DexType base = arrayType.getBaseType();
       DexType fixed = fixupType(base);
-      if (base == fixed) {
-        return type;
-      }
-      return type.replaceBaseType(fixed, factory);
+      return arrayType.replaceBaseType(fixed, factory);
     }
     return type.isClassType() && enumDataMap.isUnboxedEnum(type) ? factory.intType : type;
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
index 89b0955..1ce2f7e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.kotlin.KotlinMetadataUtils.getKotlinLocalOrAnonymousNameFromDescriptor;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexArrayType;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.lens.GraphLens;
@@ -152,10 +153,10 @@
 
   private static DexType toRewrittenTypeOrNull(AppView<?> appView, DexType type) {
     if (type.isArrayType()) {
-      DexType rewrittenBaseType =
-          toRewrittenTypeOrNull(appView, type.toBaseType(appView.dexItemFactory()));
+      DexArrayType arrayType = type.asArrayType();
+      DexType rewrittenBaseType = toRewrittenTypeOrNull(appView, arrayType.getBaseType());
       return rewrittenBaseType != null
-          ? type.replaceBaseType(rewrittenBaseType, appView.dexItemFactory())
+          ? arrayType.replaceBaseType(rewrittenBaseType, appView.dexItemFactory())
           : null;
     }
     if (!type.isClassType()) {
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index dbe8fa4..034540b 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexArrayType;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -122,8 +123,9 @@
       return type;
     }
     if (type.isArrayType()) {
-      DexType newBaseType = lookupType(type.toBaseType(dexItemFactory), dexItemFactory);
-      return type.replaceBaseType(newBaseType, dexItemFactory);
+      DexArrayType arrayType = type.asArrayType();
+      DexType newBaseType = lookupType(arrayType.getBaseType(), dexItemFactory);
+      return arrayType.replaceBaseType(newBaseType, dexItemFactory);
     }
     assert type.isClassType();
     return dexItemFactory.createType(lookupClassDescriptor(type));
@@ -227,9 +229,9 @@
         return type.getDescriptor();
       }
       if (type.isArrayType()) {
-        DexType baseType = type.toBaseType(dexItemFactory);
+        DexType baseType = type.getBaseType();
         DexString desc = lookupDescriptor(baseType);
-        return desc.toArrayDescriptor(type.getNumberOfLeadingSquareBrackets(), dexItemFactory);
+        return desc.toArrayDescriptor(type.getArrayTypeDimensions(), dexItemFactory);
       }
       assert type.isClassType();
       return lookupClassDescriptor(type);
diff --git a/src/main/java/com/android/tools/r8/relocator/RelocatorMapping.java b/src/main/java/com/android/tools/r8/relocator/RelocatorMapping.java
index fe09d4b..a835494 100644
--- a/src/main/java/com/android/tools/r8/relocator/RelocatorMapping.java
+++ b/src/main/java/com/android/tools/r8/relocator/RelocatorMapping.java
@@ -94,7 +94,7 @@
       return;
     }
     if (type.isArrayType()) {
-      computeTypeMapping(type.toBaseType(factory), factory, typeMappings, rewritePackageMappings);
+      computeTypeMapping(type.getBaseType(), factory, typeMappings, rewritePackageMappings);
       return;
     }
     assert type.isClassType();
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
index 59002e8..baca71e 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
@@ -179,7 +179,7 @@
 
   private void registerTypeAccess(DexType type, Consumer<DexClass> consumer) {
     if (type.isArrayType()) {
-      registerTypeAccess(type.toBaseType(appInfo.dexItemFactory()), consumer);
+      registerTypeAccess(type.getBaseType(), consumer);
       return;
     }
     if (type.isPrimitiveType() || type.isVoidType()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 4f26e3b..5672c39 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -174,7 +174,7 @@
 
   private static boolean isAnnotationTypeLive(
       DexAnnotation annotation, AppView<AppInfoWithLiveness> appView) {
-    DexType annotationType = annotation.annotation.type.toBaseType(appView.dexItemFactory());
+    DexType annotationType = annotation.annotation.type.getBaseType();
     return appView.appInfo().isNonProgramTypeOrLiveProgramType(annotationType);
   }
 
@@ -281,7 +281,7 @@
 
   private DexEncodedAnnotation rewriteEncodedAnnotation(DexEncodedAnnotation original) {
     GraphLens graphLens = appView.graphLens();
-    DexType annotationType = original.type.toBaseType(appView.dexItemFactory());
+    DexType annotationType = original.type.getBaseType();
     DexType rewrittenType = graphLens.lookupType(annotationType);
     DexEncodedAnnotation rewrite =
         original.rewrite(
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index a446d35..0f1eecd 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -299,7 +299,7 @@
     DexMethod values =
         factory.createMethod(
             enumType,
-            factory.createProto(factory.createArrayType(1, enumType)),
+            factory.createProto(factory.enumType.toArrayType(factory)),
             factory.valuesMethodName);
     registerInvokeStatic(values);
     DexMethod valueOf =
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index c739ce0..4d91bdb 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -736,7 +736,7 @@
       return;
     }
     if (type.isArrayType()) {
-      type = type.toBaseType(appView.dexItemFactory());
+      type = type.getBaseType();
     }
     if (!type.isClassType()) {
       return;
@@ -946,7 +946,7 @@
       Consumer<ClasspathOrLibraryClass> classAdder,
       BiConsumer<DexType, ClasspathOrLibraryDefinition> missingClassConsumer) {
     if (type.isArrayType()) {
-      type = type.toBaseType(appView.dexItemFactory());
+      type = type.getBaseType();
     }
     if (!type.isClassType()) {
       return;
@@ -1382,7 +1382,7 @@
       ListIterator<? extends CfOrDexInstruction> iterator) {
     // We conservatively group T.class and T[].class to ensure that we do not merge T with S if
     // potential locks on T[].class and S[].class exists.
-    DexType baseType = type.toBaseType(appView.dexItemFactory());
+    DexType baseType = type.getBaseType();
     if (baseType.isClassType()) {
       DexProgramClass baseClass = getProgramClassOrNull(baseType, currentMethod);
       if (baseClass != null && isConstClassMaybeUsedAsLock(currentMethod, iterator)) {
@@ -2146,7 +2146,7 @@
 
   private void markTypeAsLive(DexType type, ProgramDefinition context) {
     if (type.isArrayType()) {
-      markTypeAsLive(type.toBaseType(appView.dexItemFactory()), context);
+      markTypeAsLive(type.getBaseType(), context);
       return;
     }
     if (!type.isClassType()) {
@@ -2162,7 +2162,7 @@
 
   private void markTypeAsLive(DexType type, ProgramDefinition context, KeepReason reason) {
     if (type.isArrayType()) {
-      markTypeAsLive(type.toBaseType(appView.dexItemFactory()), context, reason);
+      markTypeAsLive(type.getBaseType(), context, reason);
       return;
     }
     if (!type.isClassType()) {
@@ -2547,7 +2547,7 @@
 
   private DexClass resolveBaseType(DexType type, ProgramDefinition context) {
     if (type.isArrayType()) {
-      return resolveBaseType(type.toBaseType(appView.dexItemFactory()), context);
+      return resolveBaseType(type.getBaseType(), context);
     }
     if (type.isClassType()) {
       DexClass clazz = appView.definitionFor(type, context.getContextClass());
@@ -3434,7 +3434,7 @@
   private void handleFieldAccessWithInaccessibleFieldType(
       ProgramField field, ProgramDefinition context) {
     if (mode.isFinalTreeShaking() && options.isOptimizing() && !field.getAccessFlags().isStatic()) {
-      DexType fieldBaseType = field.getType().toBaseType(appView.dexItemFactory());
+      DexType fieldBaseType = field.getType().getBaseType();
       if (fieldBaseType.isClassType()) {
         DexClass clazz = definitionFor(fieldBaseType, context);
         if (clazz != null
@@ -4833,7 +4833,7 @@
   private boolean verifyReferencedType(
       DexType type, WorkList<DexClass> worklist, DexApplication app) {
     if (type.isArrayType()) {
-      type = type.toBaseType(appView.dexItemFactory());
+      type = type.getBaseType();
     }
     if (!type.isClassType()) {
       return true;
@@ -5530,7 +5530,7 @@
 
     @Override
     public boolean addType(DexType type) {
-      DexType baseType = type.toBaseType(appView.dexItemFactory());
+      DexType baseType = type.getBaseType();
       if (!baseType.isClassType()) {
         return false;
       }
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
index c32e155..6491984 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
@@ -200,7 +200,7 @@
       // Note that this check depends on the set of instantiated types, and must therefore be rerun
       // when the enqueuer's fixpoint is reached.
       if (field.getType().isReferenceType()) {
-        DexType fieldBaseType = field.getType().toBaseType(appView.dexItemFactory());
+        DexType fieldBaseType = field.getType().getBaseType();
         if (fieldBaseType.isClassType()
             && mayHaveFinalizeMethodDirectlyOrIndirectly.test(fieldBaseType)) {
           return false;
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerEnumReflectionAnalysisBase.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerEnumReflectionAnalysisBase.java
index 237959c..6e2cee3 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerEnumReflectionAnalysisBase.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerEnumReflectionAnalysisBase.java
@@ -37,7 +37,7 @@
   protected DexProgramClass maybeGetProgramEnumType(DexType type, boolean checkSuper) {
     // Arrays can be used for serialization-related methods.
     if (type.isArrayType()) {
-      type = type.toBaseType(appView.dexItemFactory());
+      type = type.getBaseType();
       if (!type.isClassType()) {
         return null;
       }
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 89c25ae..c25ef76 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -90,7 +90,7 @@
     new MainDexDirectReferenceTracer(
             appView,
             type -> {
-              DexType baseType = type.toBaseType(appView.dexItemFactory());
+              DexType baseType = type.getBaseType();
               if (baseType.isClassType() && isOutside.test(baseType)) {
                 DexClass cls = appView.definitionFor(baseType);
                 if (cls != null && cls.isProgramClass()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index ffa265c..35818b6 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -126,7 +126,7 @@
         for (DexEncodedMethod method : clazz.virtualMethods()) {
           DexProto proto = method.getReference().proto;
           if (proto.parameters.isEmpty()) {
-            DexType valueType = proto.returnType.toBaseType(appView.dexItemFactory());
+            DexType valueType = proto.returnType.getBaseType();
             if (valueType.isClassType()) {
               assert !value;
               DexClass valueTypeClass = appInfo().definitionFor(valueType);
@@ -168,7 +168,7 @@
     for (DexEncodedMethod method : clazz.virtualMethods()) {
       DexProto proto = method.getReference().proto;
       if (proto.parameters.isEmpty()) {
-        DexType valueType = proto.returnType.toBaseType(appView.dexItemFactory());
+        DexType valueType = proto.returnType.getBaseType();
         if (isEnum(valueType)) {
           addDirectDependency(valueType);
         }
@@ -178,7 +178,7 @@
 
   private void addDirectDependency(DexType type) {
     // Consider only component type of arrays
-    type = type.toBaseType(appView.dexItemFactory());
+    type = type.getBaseType();
     if (!type.isClassType() || mainDexInfoBuilder.contains(type)) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 61cfa58..13653e1 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -1342,7 +1342,7 @@
         return;
       }
       if (type.isArrayType()) {
-        type = type.toBaseType(appView.dexItemFactory());
+        type = type.getBaseType();
       }
       if (type.isPrimitiveType()) {
         return;
diff --git a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
index 705edd8..714b50d 100644
--- a/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/RuntimeTypeCheckInfo.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -74,7 +73,6 @@
           TraceExceptionGuardEnqueuerAnalysis {
 
     private final GraphLens appliedGraphLens;
-    private final DexItemFactory factory;
 
     private final Set<DexType> instanceOfTypes = Sets.newIdentityHashSet();
     private final Set<DexType> checkCastTypes = Sets.newIdentityHashSet();
@@ -82,7 +80,6 @@
 
     public Builder(AppView<?> appView) {
       this.appliedGraphLens = appView.graphLens();
-      this.factory = appView.dexItemFactory();
     }
 
     public RuntimeTypeCheckInfo build(GraphLens graphLens) {
@@ -114,7 +111,7 @@
     }
 
     private void add(DexType type, Set<DexType> set) {
-      DexType baseType = type.toBaseType(factory);
+      DexType baseType = type.getBaseType();
       if (baseType.isClassType()) {
         set.add(baseType);
       }
diff --git a/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcher.java b/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcher.java
index 0bd6498..81b3cd8 100644
--- a/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcher.java
@@ -68,8 +68,7 @@
       List<KeepDeclaration> keepDeclarations,
       ExecutorService executorService)
       throws ExecutionException {
-    KeepAnnotationMatcherPredicates predicates =
-        new KeepAnnotationMatcherPredicates(appView.dexItemFactory());
+    KeepAnnotationMatcherPredicates predicates = new KeepAnnotationMatcherPredicates();
     ApplicableRulesEvaluator.Builder builder = ApplicableRulesEvaluator.initialRulesBuilder();
     ThreadUtils.processItems(
         keepDeclarations,
diff --git a/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcherPredicates.java b/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcherPredicates.java
index a307db7..22433ff 100644
--- a/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcherPredicates.java
+++ b/src/main/java/com/android/tools/r8/shaking/rules/KeepAnnotationMatcherPredicates.java
@@ -8,11 +8,11 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexArrayType;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMember;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
@@ -45,11 +45,7 @@
 
 public class KeepAnnotationMatcherPredicates {
 
-  private final DexItemFactory factory;
-
-  public KeepAnnotationMatcherPredicates(DexItemFactory factory) {
-    this.factory = factory;
-  }
+  public KeepAnnotationMatcherPredicates() {}
 
   public boolean matchesClass(
       DexClass clazz, KeepClassItemPattern classPattern, AppInfoWithClassHierarchy appInfo) {
@@ -314,14 +310,15 @@
     if (!type.isArrayType()) {
       return false;
     }
+    DexArrayType arrayType = type.asArrayType();
     if (pattern.isAny()) {
       return true;
     }
-    if (type.getArrayTypeDimensions() < pattern.getDimensions()) {
+    if (arrayType.getArrayTypeDimensions() < pattern.getDimensions()) {
       return false;
     }
     return matchesType(
-        type.toArrayElementAfterDimension(pattern.getDimensions(), factory),
+        arrayType.getArrayElementTypeAfterDimension(pattern.getDimensions()),
         pattern.getBaseType(),
         appInfo);
   }
diff --git a/src/main/java/com/android/tools/r8/tracereferences/UseCollector.java b/src/main/java/com/android/tools/r8/tracereferences/UseCollector.java
index c03b71b..0fd152a 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/UseCollector.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/UseCollector.java
@@ -318,7 +318,7 @@
   private void addType(
       DexType type, DefinitionContext referencedFrom, UseCollectorEventConsumer eventConsumer) {
     if (type.isArrayType()) {
-      addType(type.toBaseType(factory), referencedFrom, eventConsumer);
+      addType(type.getBaseType(), referencedFrom, eventConsumer);
       return;
     }
     if (type.isPrimitiveType() || type.isVoidType()) {
diff --git a/src/main/java/com/android/tools/r8/utils/AccessUtils.java b/src/main/java/com/android/tools/r8/utils/AccessUtils.java
index 84f644a..27c2297 100644
--- a/src/main/java/com/android/tools/r8/utils/AccessUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AccessUtils.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
@@ -16,8 +15,7 @@
 
   public static boolean isAccessibleInSameContextsAs(
       DexType newType, DexType oldType, AppView<AppInfoWithLiveness> appView) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    DexType newBaseType = newType.toBaseType(dexItemFactory);
+    DexType newBaseType = newType.getBaseType();
     if (!newBaseType.isClassType()) {
       return true;
     }
@@ -28,7 +26,7 @@
 
     // If the new class is not public, then the old class must be non-public as well and reside in
     // the same package as the new class.
-    DexType oldBaseType = oldType.toBaseType(dexItemFactory);
+    DexType oldBaseType = oldType.getBaseType();
     if (!newBaseClass.isPublic()) {
       assert oldBaseType.isClassType();
       DexClass oldBaseClass = appView.definitionFor(oldBaseType);
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
index a3ee59e..4f2df3a 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -91,7 +91,7 @@
     LibraryDefinition singleValueDefinition;
     if (singleValue.isSingleConstClassValue()) {
       SingleConstClassValue singleConstClassValue = singleValue.asSingleConstClassValue();
-      DexType baseType = singleConstClassValue.getType().toBaseType(appView.dexItemFactory());
+      DexType baseType = singleConstClassValue.getType().getBaseType();
       if (baseType.isPrimitiveType()) {
         return true;
       }
@@ -170,8 +170,7 @@
   }
 
   public static boolean isApiSafeForReference(DexType type, AppView<?> appView) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    DexType baseType = type.toBaseType(dexItemFactory);
+    DexType baseType = type.getBaseType();
     if (baseType.isPrimitiveType()) {
       return true;
     }
@@ -233,8 +232,7 @@
     assert newType.isReferenceType();
     assert oldType.isReferenceType();
     assert newType != oldType;
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    DexType newBaseType = newType.toBaseType(dexItemFactory);
+    DexType newBaseType = newType.getBaseType();
     if (newBaseType.isPrimitiveType()) {
       // Array of primitives is available on all api levels.
       return true;
@@ -259,7 +257,7 @@
       return true;
     }
     // Check if the new library class is present since the api level of the old type.
-    DexType oldBaseType = oldType.toBaseType(dexItemFactory);
+    DexType oldBaseType = oldType.getBaseType();
     assert oldBaseType.isClassType();
     LibraryClass oldBaseLibraryClass = asLibraryClassOrNull(appView.definitionFor(oldBaseType));
     return oldBaseLibraryClass != null
diff --git a/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java
index bb1bbc9..6333669 100644
--- a/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java
@@ -115,9 +115,8 @@
     if (typeReference.isArray()) {
       ArrayReference arrayReference = typeReference.asArray();
       TypeReference baseType = arrayReference.getBaseType();
-      return dexItemFactory.createArrayType(
-          arrayReference.getDimensions(),
-          toDexType(baseType, dexItemFactory, classReferenceConverter));
+      return toDexType(baseType, dexItemFactory, classReferenceConverter)
+          .toArrayType(dexItemFactory, arrayReference.getDimensions());
     }
     assert typeReference.isClass();
     return classReferenceConverter.apply(typeReference.asClass());
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/IllegalAccessDetector.java b/src/main/java/com/android/tools/r8/verticalclassmerging/IllegalAccessDetector.java
index 5bb0780..9ffaa5a 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/IllegalAccessDetector.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/IllegalAccessDetector.java
@@ -78,8 +78,7 @@
 
   private boolean checkRewrittenMethodReference(
       DexMethod rewrittenMethod, OptionalBool isInterface) {
-    DexType baseType =
-        rewrittenMethod.getHolderType().toBaseType(appViewWithClassHierarchy.dexItemFactory());
+    DexType baseType = rewrittenMethod.getHolderType().getBaseType();
     if (baseType.isClassType() && baseType.isSamePackage(getContext().getHolderType())) {
       if (checkRewrittenTypeReference(rewrittenMethod.getHolderType())) {
         return checkFoundPackagePrivateAccess();
@@ -115,8 +114,7 @@
 
   private boolean internalCheckTypeReference(
       DexType type, GraphLens graphLens, GraphLens codeLens) {
-    DexType baseType =
-        graphLens.lookupType(type.toBaseType(appViewWithClassHierarchy.dexItemFactory()), codeLens);
+    DexType baseType = graphLens.lookupType(type.getBaseType(), codeLens);
     if (baseType.isClassType() && baseType.isSamePackage(getContext().getHolderType())) {
       DexClass clazz = appViewWithClassHierarchy.definitionFor(baseType);
       if (clazz == null || !clazz.isPublic()) {
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoKeptClassesPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoKeptClassesPolicy.java
index f9a91a3..8d15991 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoKeptClassesPolicy.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoKeptClassesPolicy.java
@@ -129,7 +129,7 @@
   }
 
   private void markTypeAsPinned(DexType type, Set<DexProgramClass> pinnedClasses) {
-    DexType baseType = type.toBaseType(appView.dexItemFactory());
+    DexType baseType = type.getBaseType();
     if (!baseType.isClassType()) {
       return;
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/B141942381.java b/src/test/java/com/android/tools/r8/classmerging/vertical/B141942381.java
index 3063698..b10ede7 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/B141942381.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/B141942381.java
@@ -82,7 +82,7 @@
 
     assertEquals(
         set.getMethod().getReference().proto.parameters.values[0],
-        storage.getField().getReference().type.toBaseType(inspector.getFactory()));
+        storage.getField().getReference().type.getBaseType());
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
index c24c156..aa63d01 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
@@ -98,7 +98,7 @@
     MultiAPILevelMachineDesugaredLibrarySpecification machineSpec1 =
         converter2.convertAllAPILevels(humanSpec2, app);
     MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.export(
-        machineSpec1, (string, handler) -> json2.set(string), options.dexItemFactory());
+        machineSpec1, (string, handler) -> json2.set(string));
 
     MachineDesugaredLibrarySpecification machineSpecParsed =
         new MachineDesugaredLibrarySpecificationParser(
@@ -194,7 +194,7 @@
     MultiAPILevelMachineDesugaredLibrarySpecification machineSpec1 =
         converter2.convertAllAPILevels(humanSpec2, app);
     MultiAPILevelMachineDesugaredLibrarySpecificationJsonExporter.export(
-        machineSpec1, (string, handler) -> json2.set(string), options.dexItemFactory());
+        machineSpec1, (string, handler) -> json2.set(string));
 
     for (AndroidApiLevel api :
         new AndroidApiLevel[] {AndroidApiLevel.B, AndroidApiLevel.N, AndroidApiLevel.O}) {
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index dde28d7..6364e7c 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -98,7 +98,7 @@
   }
 
   private ArrayTypeElement array(int nesting, DexType base) {
-    return (ArrayTypeElement) element(factory.createArrayType(nesting, base));
+    return (ArrayTypeElement) element(base.toArrayType(factory, nesting));
   }
 
   private ArrayTypeElement array(TypeElement baseType) {
diff --git a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
index a9da63b..d44d061 100644
--- a/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/ArrayTargetLookupTest.java
@@ -61,7 +61,7 @@
           factory.intArrayType,
           factory.stringArrayType,
           factory.objectArrayType,
-          factory.createArrayType(2, fooType)
+          fooType.toArrayType(factory, 2)
         };
     DexEncodedMethod langObjectNotifyMethod =
         appInfo
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index a3e86f6..721d394 100644
--- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -597,7 +597,7 @@
       @Override
       protected boolean matchesSafely(FieldSubject fieldSubject) {
         return fieldSubject.getDexField().type.isArrayType()
-            && fieldSubject.getDexField().type.toBaseType(codeInspector.getFactory()) == type;
+            && fieldSubject.getDexField().type.getBaseType() == type;
       }
 
       @Override