Structural definitions for encoded fields, annotations and dex values.

Bug: 171867022
Change-Id: I4357f6791494cea2bdbc008dccdfc69dd3eec05b
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index de254fc..99f8c14 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -4,12 +4,15 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.structural.StructuralAccept;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.function.BooleanSupplier;
 
 /** Access flags common to classes, methods and fields. */
-public abstract class AccessFlags<T extends AccessFlags<T>> {
+public abstract class AccessFlags<T extends AccessFlags<T>> implements StructuralItem<T> {
 
   protected static final int BASE_FLAGS
       = Constants.ACC_PUBLIC
@@ -53,6 +56,15 @@
     this.modifiedFlags = modifiedFlags;
   }
 
+  protected static <T extends AccessFlags<T>> void specify(StructuralSpecification<T, ?> spec) {
+    spec.withInt(a -> a.originalFlags).withInt(a -> a.modifiedFlags);
+  }
+
+  @Override
+  public StructuralAccept<T> getStructuralAccept() {
+    return AccessFlags::specify;
+  }
+
   public abstract T copy();
 
   public abstract T self();
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationElement.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationElement.java
index 237838f..fc764ff 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationElement.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationElement.java
@@ -5,18 +5,35 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.utils.structural.StructuralAccept;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
 
-public class DexAnnotationElement extends DexItem {
+public class DexAnnotationElement extends DexItem implements StructuralItem<DexAnnotationElement> {
   public static final DexAnnotationElement[] EMPTY_ARRAY = {};
 
   public final DexString name;
   public final DexValue value;
 
+  private static void specify(StructuralSpecification<DexAnnotationElement, ?> spec) {
+    spec.withItem(e -> e.name).withItem(e -> e.value);
+  }
+
   public DexAnnotationElement(DexString name, DexValue value) {
     this.name = name;
     this.value = value;
   }
 
+  @Override
+  public DexAnnotationElement self() {
+    return this;
+  }
+
+  @Override
+  public StructuralAccept<DexAnnotationElement> getStructuralAccept() {
+    return DexAnnotationElement::specify;
+  }
+
   public DexValue getValue() {
     return value;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
index 051dd92..bcefcd2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
@@ -6,11 +6,14 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.utils.ArrayUtils;
+import com.android.tools.r8.utils.structural.StructuralAccept;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
 import java.util.Arrays;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
-public class DexEncodedAnnotation extends DexItem {
+public class DexEncodedAnnotation extends DexItem implements StructuralItem<DexEncodedAnnotation> {
 
   private static final int UNSORTED = 0;
 
@@ -19,11 +22,25 @@
 
   private int sorted = UNSORTED;
 
+  private static void specify(StructuralSpecification<DexEncodedAnnotation, ?> spec) {
+    spec.withItem(a -> a.type).withItemArray(a -> a.elements);
+  }
+
   public DexEncodedAnnotation(DexType type, DexAnnotationElement[] elements) {
     this.type = type;
     this.elements = elements;
   }
 
+  @Override
+  public DexEncodedAnnotation self() {
+    return this;
+  }
+
+  @Override
+  public StructuralAccept<DexEncodedAnnotation> getStructuralAccept() {
+    return DexEncodedAnnotation::specify;
+  }
+
   public void collectIndexedItems(IndexedItemCollection indexedItems) {
     type.collectIndexedItems(indexedItems);
     for (DexAnnotationElement element : elements) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 5f58f30..133ebe5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -20,8 +20,12 @@
 import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
 import com.android.tools.r8.kotlin.KotlinFieldLevelInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.structural.StructuralAccept;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
 
-public class DexEncodedField extends DexEncodedMember<DexEncodedField, DexField> {
+public class DexEncodedField extends DexEncodedMember<DexEncodedField, DexField>
+    implements StructuralItem<DexEncodedField> {
   public static final DexEncodedField[] EMPTY_ARRAY = {};
 
   public final DexField field;
@@ -34,6 +38,16 @@
   private FieldOptimizationInfo optimizationInfo = DefaultFieldOptimizationInfo.getInstance();
   private KotlinFieldLevelInfo kotlinMemberInfo = NO_KOTLIN_INFO;
 
+  private static void specify(StructuralSpecification<DexEncodedField, ?> spec) {
+    spec.withItem(f -> f.field)
+        .withItem(f -> f.accessFlags)
+        .withNullableItem(f -> f.staticValue)
+        .withBool(f -> f.deprecated)
+        // TODO(b/171867022): The generic signature should be part of the definition.
+        .withAssert(f -> f.genericSignature.hasNoSignature());
+    // TODO(b/171867022): Should the optimization info and member info be part of the definition?
+  }
+
   public DexEncodedField(
       DexField field,
       FieldAccessFlags accessFlags,
@@ -60,6 +74,16 @@
     this(field, accessFlags, genericSignature, annotations, staticValue, false);
   }
 
+  @Override
+  public StructuralAccept<DexEncodedField> getStructuralAccept() {
+    return DexEncodedField::specify;
+  }
+
+  @Override
+  public DexEncodedField self() {
+    return this;
+  }
+
   public DexType type() {
     return field.type;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexReference.java b/src/main/java/com/android/tools/r8/graph/DexReference.java
index d32c7ba..8a59614 100644
--- a/src/main/java/com/android/tools/r8/graph/DexReference.java
+++ b/src/main/java/com/android/tools/r8/graph/DexReference.java
@@ -63,7 +63,7 @@
     return null;
   }
 
-  private int referenceTypeOrder() {
+  public int referenceTypeOrder() {
     if (isDexType()) {
       return 1;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
index e4462ac..595c5dd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -7,12 +7,10 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.utils.ArrayUtils;
-import com.android.tools.r8.utils.structural.CompareToVisitor;
-import com.android.tools.r8.utils.structural.HashingVisitor;
 import com.android.tools.r8.utils.structural.StructuralAccept;
 import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
 import com.google.common.collect.Iterators;
 import java.util.Arrays;
 import java.util.Collection;
@@ -27,6 +25,10 @@
 
   public final DexType[] values;
 
+  private static void specify(StructuralSpecification<DexTypeList, ?> spec) {
+    spec.withItemArray(ts -> ts.values);
+  }
+
   public static DexTypeList empty() {
     return theEmptyTypeList;
   }
@@ -52,12 +54,6 @@
     return values.isEmpty() ? DexTypeList.empty() : new DexTypeList(values);
   }
 
-  @Override
-  public StructuralAccept<DexTypeList> getStructuralAccept() {
-    // Structural accept is never accessed as all accept methods are defined directly.
-    throw new Unreachable();
-  }
-
   public DexTypeList keepIf(Predicate<DexType> predicate) {
     DexType[] filtered = ArrayUtils.filter(DexType[].class, values, predicate);
     if (filtered != values) {
@@ -76,13 +72,8 @@
   }
 
   @Override
-  public void acceptCompareTo(DexTypeList other, CompareToVisitor visitor) {
-    visitor.visitDexTypeList(this, other);
-  }
-
-  @Override
-  public void acceptHashing(HashingVisitor visitor) {
-    visitor.visitDexTypeList(this);
+  public StructuralAccept<DexTypeList> getStructuralAccept() {
+    return DexTypeList::specify;
   }
 
   public boolean contains(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index 273c69a..a54daf8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -23,12 +23,16 @@
 import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.EncodedValueUtils;
+import com.android.tools.r8.utils.structural.CompareToVisitor;
+import com.android.tools.r8.utils.structural.HashingVisitor;
+import com.android.tools.r8.utils.structural.StructuralAccept;
+import com.android.tools.r8.utils.structural.StructuralItem;
 import java.util.Arrays;
 import java.util.function.Consumer;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.Type;
 
-public abstract class DexValue extends DexItem {
+public abstract class DexValue extends DexItem implements StructuralItem<DexValue> {
 
   public enum DexValueKind {
     BYTE(0x00),
@@ -104,6 +108,41 @@
     }
   }
 
+  @Override
+  public DexValue self() {
+    return this;
+  }
+
+  @Override
+  public final StructuralAccept<DexValue> getStructuralAccept() {
+    // DexValue is not generic at its base type (and can't as we use it as a polymorphic value),
+    // so each concrete value must implement polymorphic accept functions. This base class
+    // implements (most of) the polymorphic checks and concrete types implement the internal
+    // variants for that type.
+    throw new Unreachable();
+  }
+
+  @Override
+  public final void acceptCompareTo(DexValue other, CompareToVisitor visitor) {
+    // Order first on 'kind', only equal kinds then forward to the 'kind' specific internal compare.
+    if (getValueKind() != other.getValueKind()) {
+      visitor.visitInt(getValueKind().toByte(), other.getValueKind().toByte());
+    } else {
+      internalAcceptCompareTo(other, visitor);
+    }
+  }
+
+  @Override
+  public final void acceptHashing(HashingVisitor visitor) {
+    // Always hash the 'kind' which ensures that raw values of different type are distinct.
+    visitor.visitInt(getValueKind().toByte());
+    internalAcceptHashing(visitor);
+  }
+
+  abstract void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor);
+
+  abstract void internalAcceptHashing(HashingVisitor visitor);
+
   public static final DexValue[] EMPTY_ARRAY = {};
 
   public abstract DexValueKind getValueKind();
@@ -438,6 +477,16 @@
       return value == DEFAULT.value ? DEFAULT : new DexValueByte(value);
     }
 
+    @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      visitor.visitInt(value, other.asDexValueByte().value);
+    }
+
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      visitor.visitInt(value);
+    }
+
     public byte getValue() {
       return value;
     }
@@ -521,6 +570,16 @@
       return value == DEFAULT.value ? DEFAULT : new DexValueShort(value);
     }
 
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      visitor.visitInt(value);
+    }
+
+    @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      visitor.visitInt(value, other.asDexValueShort().getValue());
+    }
+
     public short getValue() {
       return value;
     }
@@ -603,6 +662,16 @@
       return value == DEFAULT.value ? DEFAULT : new DexValueChar(value);
     }
 
+    @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      visitor.visitInt(value, other.asDexValueChar().value);
+    }
+
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      visitor.visitInt(value);
+    }
+
     public char getValue() {
       return value;
     }
@@ -689,6 +758,16 @@
       return value == DEFAULT.value ? DEFAULT : new DexValueInt(value);
     }
 
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      visitor.visitInt(value);
+    }
+
+    @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      visitor.visitInt(value, other.asDexValueInt().value);
+    }
+
     public int getValue() {
       return value;
     }
@@ -771,6 +850,16 @@
       return value == DEFAULT.value ? DEFAULT : new DexValueLong(value);
     }
 
+    @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      visitor.visitLong(value, other.asDexValueLong().value);
+    }
+
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      visitor.visitLong(value);
+    }
+
     public long getValue() {
       return value;
     }
@@ -853,6 +942,16 @@
       return Float.compare(value, DEFAULT.value) == 0 ? DEFAULT : new DexValueFloat(value);
     }
 
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      visitor.visitFloat(value);
+    }
+
+    @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      visitor.visitFloat(value, other.asDexValueFloat().value);
+    }
+
     public float getValue() {
       return value;
     }
@@ -941,6 +1040,16 @@
       return Double.compare(value, DEFAULT.value) == 0 ? DEFAULT : new DexValueDouble(value);
     }
 
+    @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      visitor.visitDouble(value, other.asDexValueDouble().value);
+    }
+
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      visitor.visitDouble(value);
+    }
+
     public double getValue() {
       return value;
     }
@@ -1087,6 +1196,18 @@
     }
 
     @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      value.acceptHashing(visitor);
+    }
+
+    @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      if (DexItemBasedValueString.compareAndCheckValueStrings(this, other, visitor)) {
+        value.acceptCompareTo(other.asDexValueString().value, visitor);
+      }
+    }
+
+    @Override
     public void collectIndexedItems(IndexedItemCollection indexedItems) {
       value.collectIndexedItems(indexedItems);
     }
@@ -1143,6 +1264,20 @@
 
   public static class DexItemBasedValueString extends NestedDexValue<DexReference> {
 
+    // Helper to ensure a consistent order on DexValueString and DexItemBasedValueString which are
+    // both defined to have kind 'string'.
+    static boolean compareAndCheckValueStrings(DexValue v1, DexValue v2, CompareToVisitor visitor) {
+      assert v1.getValueKind() == DexValueKind.STRING;
+      assert v2.getValueKind() == DexValueKind.STRING;
+      int order1 = v1.isDexItemBasedValueString() ? 1 : 0;
+      int order2 = v2.isDexItemBasedValueString() ? 1 : 0;
+      boolean equal = order1 == order2;
+      if (!equal) {
+        visitor.visitInt(order1, order2);
+      }
+      return equal;
+    }
+
     private final NameComputationInfo<?> nameComputationInfo;
 
     public DexItemBasedValueString(DexReference value, NameComputationInfo<?> nameComputationInfo) {
@@ -1151,6 +1286,18 @@
     }
 
     @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      visitor.visitDexReference(value);
+    }
+
+    @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      if (compareAndCheckValueStrings(this, other, visitor)) {
+        visitor.visitDexReference(value, other.asDexItemBasedValueString().value);
+      }
+    }
+
+    @Override
     public void collectIndexedItems(IndexedItemCollection indexedItems) {
       value.collectIndexedItems(indexedItems);
     }
@@ -1221,6 +1368,16 @@
     }
 
     @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      value.acceptCompareTo(other.asDexValueType().value, visitor);
+    }
+
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      value.acceptHashing(visitor);
+    }
+
+    @Override
     public DexValueKind getValueKind() {
       return DexValueKind.TYPE;
     }
@@ -1253,6 +1410,16 @@
     }
 
     @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      value.acceptCompareTo(other.asDexValueField().value, visitor);
+    }
+
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      value.acceptHashing(visitor);
+    }
+
+    @Override
     public DexValueKind getValueKind() {
       return DexValueKind.FIELD;
     }
@@ -1285,6 +1452,16 @@
     }
 
     @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      value.acceptCompareTo(other.asDexValueMethod().value, visitor);
+    }
+
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      value.acceptHashing(visitor);
+    }
+
+    @Override
     public DexValueKind getValueKind() {
       return DexValueKind.METHOD;
     }
@@ -1317,6 +1494,16 @@
     }
 
     @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      value.acceptCompareTo(other.asDexValueEnum().value, visitor);
+    }
+
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      value.acceptHashing(visitor);
+    }
+
+    @Override
     public DexValueKind getValueKind() {
       return DexValueKind.ENUM;
     }
@@ -1349,6 +1536,16 @@
     }
 
     @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      value.acceptCompareTo(other.asDexValueMethodType().value, visitor);
+    }
+
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      value.acceptHashing(visitor);
+    }
+
+    @Override
     public boolean isDexValueMethodType() {
       return true;
     }
@@ -1382,6 +1579,16 @@
       this.values = values;
     }
 
+    @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      visitor.visitItemArray(values, other.asDexValueArray().values);
+    }
+
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      visitor.visitItemArray(values);
+    }
+
     public void forEachElement(Consumer<DexValue> consumer) {
       for (DexValue value : values) {
         consumer.accept(value);
@@ -1481,6 +1688,16 @@
       this.value = value;
     }
 
+    @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      value.acceptCompareTo(other.asDexValueAnnotation().value, visitor);
+    }
+
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      value.acceptHashing(visitor);
+    }
+
     public DexEncodedAnnotation getValue() {
       return value;
     }
@@ -1567,6 +1784,17 @@
     private DexValueNull() {
     }
 
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      assert this == NULL;
+    }
+
+    @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      assert this == NULL;
+      assert other == NULL;
+    }
+
     public Object getValue() {
       return null;
     }
@@ -1653,6 +1881,16 @@
       return value ? TRUE : FALSE;
     }
 
+    @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      visitor.visitBool(value, other.asDexValueBoolean().value);
+    }
+
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      visitor.visitBool(value);
+    }
+
     public boolean getValue() {
       return value;
     }
@@ -1729,6 +1967,16 @@
     }
 
     @Override
+    void internalAcceptCompareTo(DexValue other, CompareToVisitor visitor) {
+      value.acceptCompareTo(other.asDexValueMethodHandle().value, visitor);
+    }
+
+    @Override
+    void internalAcceptHashing(HashingVisitor visitor) {
+      value.acceptHashing(visitor);
+    }
+
+    @Override
     public boolean isDexValueMethodHandle() {
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitor.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitor.java
index 832d172..47d8669 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitor.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitor.java
@@ -5,10 +5,14 @@
 
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.utils.structural.StructuralItem.CompareToAccept;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Comparator;
+import java.util.Iterator;
 
 /** Base class for a visitor implementing compareTo on a structural item. */
 public abstract class CompareToVisitor {
@@ -17,13 +21,30 @@
 
   public abstract void visitInt(int value1, int value2);
 
+  public abstract void visitLong(long value1, long value2);
+
+  public abstract void visitFloat(float value1, float value2);
+
+  public abstract void visitDouble(double value1, double value2);
+
+  /** Base for visiting an enumeration of items. */
+  protected abstract <S> void visitItemIterator(
+      Iterator<S> it1, Iterator<S> it2, CompareToAccept<S> compareToAccept);
+
+  public final <S extends StructuralItem<S>> void visitItemArray(S[] items1, S[] items2) {
+    visitItemCollection(Arrays.asList(items1), Arrays.asList(items2));
+  }
+
+  public final <S extends StructuralItem<S>> void visitItemCollection(
+      Collection<S> items1, Collection<S> items2) {
+    visitItemIterator(items1.iterator(), items2.iterator(), S::acceptCompareTo);
+  }
+
   public abstract void visitDexString(
       DexString string1, DexString string2, Comparator<DexString> comparator);
 
   public abstract void visitDexType(DexType type1, DexType type2);
 
-  public abstract void visitDexTypeList(DexTypeList types1, DexTypeList types2);
-
   public void visitDexField(DexField field1, DexField field2) {
     visit(field1, field2, field1.getStructuralAccept());
   }
@@ -32,6 +53,8 @@
     visit(method1, method2, method1.getStructuralAccept());
   }
 
+  public abstract void visitDexReference(DexReference reference1, DexReference reference2);
+
   public abstract <S> void visit(S item1, S item2, StructuralAccept<S> accept);
 
   @Deprecated
diff --git a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorBase.java b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorBase.java
index b7d016a..3526dfb 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorBase.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/CompareToVisitorBase.java
@@ -3,11 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.structural;
 
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.utils.structural.StructuralItem.CompareToAccept;
 import com.android.tools.r8.utils.structural.StructuralItem.HashingAccept;
 import java.util.Comparator;
+import java.util.Iterator;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.ToIntFunction;
@@ -44,6 +45,38 @@
   }
 
   @Override
+  public void visitLong(long value1, long value2) {
+    if (stillEqual()) {
+      setOrder(Long.compare(value1, value2));
+    }
+  }
+
+  @Override
+  public void visitFloat(float value1, float value2) {
+    if (stillEqual()) {
+      setOrder(Float.compare(value1, value2));
+    }
+  }
+
+  @Override
+  public void visitDouble(double value1, double value2) {
+    if (stillEqual()) {
+      setOrder(Double.compare(value1, value2));
+    }
+  }
+
+  @Override
+  protected <S> void visitItemIterator(
+      Iterator<S> it1, Iterator<S> it2, CompareToAccept<S> compareToAccept) {
+    while (stillEqual() && it1.hasNext() && it2.hasNext()) {
+      compareToAccept.accept(it1.next(), it2.next(), this);
+    }
+    if (stillEqual()) {
+      visitBool(it1.hasNext(), it2.hasNext());
+    }
+  }
+
+  @Override
   public final void visitDexString(
       DexString string1, DexString string2, Comparator<DexString> comparator) {
     if (stillEqual()) {
@@ -52,15 +85,18 @@
   }
 
   @Override
-  public final void visitDexTypeList(DexTypeList types1, DexTypeList types2) {
-    // Comparison is lexicographic with comparisons between items prior to the length of the lists.
+  public void visitDexReference(DexReference reference1, DexReference reference2) {
     if (stillEqual()) {
-      int length = Math.min(types1.size(), types2.size());
-      for (int i = 0; i < length && stillEqual(); i++) {
-        visitDexType(types1.values[i], types2.values[i]);
-      }
+      visitInt(reference1.referenceTypeOrder(), reference2.referenceTypeOrder());
       if (stillEqual()) {
-        visitInt(types1.size(), types2.size());
+        assert reference1.getClass() == reference2.getClass();
+        if (reference1.isDexType()) {
+          visitDexType(reference1.asDexType(), reference2.asDexType());
+        } else if (reference1.isDexField()) {
+          visitDexField(reference1.asDexField(), reference2.asDexField());
+        } else {
+          visitDexMethod(reference1.asDexMethod(), reference2.asDexMethod());
+        }
       }
     }
   }
@@ -128,5 +164,14 @@
       }
       return this;
     }
+
+    @Override
+    protected <S> ItemSpecification<T> withItemIterator(
+        Function<T, Iterator<S>> getter, CompareToAccept<S> compare, HashingAccept<S> hasher) {
+      if (parent.stillEqual()) {
+        parent.visitItemIterator(getter.apply(item1), getter.apply(item2), compare);
+      }
+      return this;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/structural/HashCodeVisitor.java b/src/main/java/com/android/tools/r8/utils/structural/HashCodeVisitor.java
index ab75fb4..8038e48 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/HashCodeVisitor.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/HashCodeVisitor.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.utils.structural.StructuralItem.CompareToAccept;
 import com.android.tools.r8.utils.structural.StructuralItem.HashingAccept;
+import java.util.Iterator;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.ToIntFunction;
@@ -64,9 +65,19 @@
     if (predicate.test(item)) {
       return amend(getter.apply(item).hashCode());
     } else {
-      // Use the value 1 for the failing-predicate case such that a different hash is obtained for
+      // Use the value 1 for the failing-predicate case such that a different hash is obtained for,
       // eg, {null, null} and {null}.
       return amend(1);
     }
   }
+
+  @Override
+  protected <S> HashCodeVisitor<T> withItemIterator(
+      Function<T, Iterator<S>> getter, CompareToAccept<S> compare, HashingAccept<S> hasher) {
+    Iterator<S> it = getter.apply(item);
+    while (it.hasNext()) {
+      amend(it.next().hashCode());
+    }
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/structural/HashingVisitor.java b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitor.java
index bfd5e0d..ed0f61e 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/HashingVisitor.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitor.java
@@ -3,10 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils.structural;
 
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.utils.structural.StructuralItem.HashingAccept;
 import com.google.common.hash.Hasher;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
 import java.util.function.BiConsumer;
 
 public abstract class HashingVisitor {
@@ -15,11 +21,38 @@
 
   public abstract void visitInt(int value);
 
+  public abstract void visitFloat(float value);
+
+  public abstract void visitLong(long value);
+
+  public abstract void visitDouble(double value);
+
+  /** Base for visiting an enumeration of items. */
+  protected abstract <S> void visitItemIterator(Iterator<S> it, HashingAccept<S> hashingAccept);
+
+  public final <S extends StructuralItem<S>> void visitItemArray(S[] items) {
+    visitItemCollection(Arrays.asList(items));
+  }
+
+  public final <S extends StructuralItem<S>> void visitItemCollection(Collection<S> items) {
+    visitItemIterator(items.iterator(), S::acceptHashing);
+  }
+
   public abstract void visitDexString(DexString string);
 
   public abstract void visitDexType(DexType type);
 
-  public abstract void visitDexTypeList(DexTypeList types);
+  public void visitDexField(DexField field) {
+    visit(field, field.getStructuralAccept());
+  }
+
+  public void visitDexMethod(DexMethod method) {
+    visit(method, method.getStructuralAccept());
+  }
+
+  public void visitDexReference(DexReference reference) {
+    reference.accept(this::visitDexType, this::visitDexField, this::visitDexMethod);
+  }
 
   public abstract <S> void visit(S item, StructuralAccept<S> accept);
 
diff --git a/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java
index 2036b1b..d216cd9 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/HashingVisitorWithTypeEquivalence.java
@@ -5,10 +5,10 @@
 
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.utils.structural.StructuralItem.CompareToAccept;
 import com.android.tools.r8.utils.structural.StructuralItem.HashingAccept;
 import com.google.common.hash.Hasher;
+import java.util.Iterator;
 import java.util.function.BiConsumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -46,6 +46,21 @@
   }
 
   @Override
+  public void visitFloat(float value) {
+    hash.putFloat(value);
+  }
+
+  @Override
+  public void visitLong(long value) {
+    hash.putLong(value);
+  }
+
+  @Override
+  public void visitDouble(double value) {
+    hash.putDouble(value);
+  }
+
+  @Override
   public void visitDexString(DexString string) {
     visitInt(string.hashCode());
   }
@@ -56,13 +71,15 @@
   }
 
   @Override
-  public void visitDexTypeList(DexTypeList types) {
-    types.forEach(this::visitDexType);
+  public <S> void visit(S item, StructuralAccept<S> accept) {
+    accept.accept(new ItemSpecification<>(item, this));
   }
 
   @Override
-  public <S> void visit(S item, StructuralAccept<S> accept) {
-    accept.accept(new ItemSpecification<>(item, this));
+  protected <S> void visitItemIterator(Iterator<S> it, HashingAccept<S> hashingAccept) {
+    while (it.hasNext()) {
+      hashingAccept.accept(it.next(), this);
+    }
   }
 
   @Override
@@ -113,5 +130,12 @@
       }
       return this;
     }
+
+    @Override
+    protected <S> ItemSpecification<T> withItemIterator(
+        Function<T, Iterator<S>> getter, CompareToAccept<S> compare, HashingAccept<S> hasher) {
+      parent.visitItemIterator(getter.apply(item), hasher);
+      return this;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/structural/StructuralSpecification.java b/src/main/java/com/android/tools/r8/utils/structural/StructuralSpecification.java
index bb8f7c7..b80d95a 100644
--- a/src/main/java/com/android/tools/r8/utils/structural/StructuralSpecification.java
+++ b/src/main/java/com/android/tools/r8/utils/structural/StructuralSpecification.java
@@ -5,6 +5,9 @@
 
 import com.android.tools.r8.utils.structural.StructuralItem.CompareToAccept;
 import com.android.tools.r8.utils.structural.StructuralItem.HashingAccept;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.ToIntFunction;
@@ -12,12 +15,12 @@
 public abstract class StructuralSpecification<T, V extends StructuralSpecification<T, V>> {
 
   /**
-   * Basic specification for visiting an item.
+   * Base for accessing and visiting a sub-part on an item.
    *
-   * <p>This specified the getter for the item as well as all of the methods that are required for
-   * visiting. Those coincide with the requirements of Specified.
+   * <p>This specifies the getter for the sub-part as well as all of the methods that are required
+   * for visiting. The required methods coincide with the requirements of StructuralItem.
    *
-   * <p>It is preferable to use withStructuralItem.
+   * <p>It is preferable to use withItem and make the item itself implement StructuralItem.
    */
   @Deprecated
   public final <S> V withCustomItem(
@@ -25,12 +28,17 @@
     return withConditionalCustomItem(t -> true, getter, compare, hasher);
   }
 
+  /** Base implementation for visiting an item. */
   protected abstract <S> V withConditionalCustomItem(
       Predicate<T> predicate,
       Function<T, S> getter,
       CompareToAccept<S> compare,
       HashingAccept<S> hasher);
 
+  /** Base implementation for visiting an enumeration of items. */
+  protected abstract <S> V withItemIterator(
+      Function<T, Iterator<S>> getter, CompareToAccept<S> compare, HashingAccept<S> hasher);
+
   /**
    * Specification for a "specified" item.
    *
@@ -41,7 +49,7 @@
     return withConditionalItem(t -> true, getter);
   }
 
-  final <S extends StructuralItem<S>> V withNullableItem(Function<T, S> getter) {
+  public final <S extends StructuralItem<S>> V withNullableItem(Function<T, S> getter) {
     return withConditionalItem(s -> getter.apply(s) != null, getter);
   }
 
@@ -50,6 +58,17 @@
     return withConditionalCustomItem(predicate, getter, S::acceptCompareTo, S::acceptHashing);
   }
 
+  public final <S extends StructuralItem<S>> V withItemCollection(
+      Function<T, Collection<S>> getter) {
+    return withItemIterator(
+        getter.andThen(Collection::iterator), S::acceptCompareTo, S::acceptHashing);
+  }
+
+  public final <S extends StructuralItem<S>> V withItemArray(Function<T, S[]> getter) {
+    return withItemIterator(
+        getter.andThen(a -> Arrays.asList(a).iterator()), S::acceptCompareTo, S::acceptHashing);
+  }
+
   /**
    * Helper to declare an assert on the item.
    *