Fix signature collision after field type strengthening

Bug: b/231030461
Change-Id: I4ae6ccb9e5e683a7a43df86db95eb4332719a1a9
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index 6b94973..1bc719c 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -55,6 +55,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -441,10 +442,24 @@
         DexMethodSignatureSet interfaceDispatchOutsideProgram,
         ArgumentPropagatorGraphLens.Builder partialGraphLensBuilder) {
       BooleanBox affected = new BooleanBox();
+      Set<DexField> newFieldSignatures = Sets.newIdentityHashSet();
+      Map<DexField, DexType> newFieldTypes = new IdentityHashMap<>();
       clazz.forEachProgramFieldMatching(
           field -> field.getType().isClassType(),
           field -> {
-            DexField newFieldSignature = getNewFieldSignature(field);
+            DexType newFieldType = getNewFieldType(field);
+            if (newFieldType != field.getType()) {
+              newFieldTypes.put(field.getReference(), newFieldType);
+            } else {
+              // Reserve field signature.
+              newFieldSignatures.add(field.getReference());
+            }
+          });
+      clazz.forEachProgramFieldMatching(
+          field -> field.getType().isClassType(),
+          field -> {
+            DexField newFieldSignature =
+                getNewFieldSignature(field, newFieldSignatures, newFieldTypes);
             if (newFieldSignature != field.getReference()) {
               partialGraphLensBuilder.recordMove(field.getReference(), newFieldSignature);
               affected.set();
@@ -469,10 +484,10 @@
       return affected.get();
     }
 
-    private DexField getNewFieldSignature(ProgramField field) {
+    private DexType getNewFieldType(ProgramField field) {
       DynamicType dynamicType = field.getOptimizationInfo().getDynamicType();
       if (dynamicType.isUnknown()) {
-        return field.getReference();
+        return field.getType();
       }
 
       KeepFieldInfo keepInfo = appView.getKeepInfo(field);
@@ -481,17 +496,17 @@
       assert !keepInfo.isPinned(options);
 
       if (!keepInfo.isFieldTypeStrengtheningAllowed(options)) {
-        return field.getReference();
+        return field.getType();
       }
 
       if (dynamicType.isNullType()) {
         // Don't optimize always null fields; these will be optimized anyway.
-        return field.getReference();
+        return field.getType();
       }
 
       if (dynamicType.isNotNullType()) {
         // We don't have a more specific type.
-        return field.getReference();
+        return field.getType();
       }
 
       DynamicTypeWithUpperBound dynamicTypeWithUpperBound =
@@ -502,12 +517,12 @@
       ClassTypeElement staticFieldType = field.getType().toTypeElement(appView).asClassType();
       if (dynamicUpperBoundType.equalUpToNullability(staticFieldType)) {
         // We don't have more precise type information.
-        return field.getReference();
+        return field.getType();
       }
 
       if (!dynamicUpperBoundType.strictlyLessThan(staticFieldType, appView)) {
         assert options.testing.allowTypeErrors;
-        return field.getReference();
+        return field.getType();
       }
 
       DexType newStaticFieldType;
@@ -518,7 +533,7 @@
             newStaticFieldType =
                 dynamicUpperBoundClassType.getInterfaces().getSingleKnownInterface();
           } else {
-            return field.getReference();
+            return field.getType();
           }
         } else {
           newStaticFieldType = dynamicUpperBoundClassType.getClassType();
@@ -528,10 +543,24 @@
       }
 
       if (!AccessUtils.isAccessibleInSameContextsAs(newStaticFieldType, field.getType(), appView)) {
-        return field.getReference();
+        return field.getType();
       }
 
-      return field.getReference().withType(newStaticFieldType, dexItemFactory);
+      return newStaticFieldType;
+    }
+
+    private DexField getNewFieldSignature(
+        ProgramField field,
+        Set<DexField> newFieldSignatures,
+        Map<DexField, DexType> newFieldTypes) {
+      DexType newFieldType = newFieldTypes.getOrDefault(field.getReference(), field.getType());
+      if (newFieldType == field.getType()) {
+        assert newFieldSignatures.contains(field.getReference());
+        return field.getReference();
+      }
+      // Find a new name for this field if the signature is already occupied.
+      return dexItemFactory.createFreshFieldNameWithoutHolder(
+          field.getHolderType(), newFieldType, field.getName().toString(), newFieldSignatures::add);
     }
 
     private DexMethod getNewMethodSignature(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldTypeStrengtheningCollisionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldTypeStrengtheningCollisionTest.java
index b79fb5c..9e5e433 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldTypeStrengtheningCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldTypeStrengtheningCollisionTest.java
@@ -4,12 +4,19 @@
 
 package com.android.tools.r8.ir.optimize.membervaluepropagation.fields;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.fields.FieldTypeStrengtheningTest.A;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.fields.FieldTypeStrengtheningTest.Main;
 import com.android.tools.r8.transformers.ClassFileTransformer.FieldPredicate;
-import com.android.tools.r8.utils.codeinspector.AssertUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import java.io.IOException;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -30,21 +37,36 @@
 
   @Test
   public void test() throws Exception {
-    AssertUtils.assertFailsCompilation(
-        () ->
-            testForR8(parameters.getBackend())
-                .addProgramClasses(A.class)
-                .addProgramClassFileData(getTransformedMain())
-                .addKeepMainRule(Main.class)
-                .addKeepRules(
-                    "-keep class "
-                        + Main.class.getTypeName()
-                        + " { "
-                        + A.class.getTypeName()
-                        + " f; }")
-                .enableInliningAnnotations()
-                .setMinApi(parameters.getApiLevel())
-                .compile());
+    testForR8(parameters.getBackend())
+        .addProgramClasses(A.class)
+        .addProgramClassFileData(getTransformedMain())
+        .addKeepMainRule(Main.class)
+        .addKeepRules(
+            "-keep class " + Main.class.getTypeName() + " { " + A.class.getTypeName() + " f; }")
+        .enableInliningAnnotations()
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+
+              FieldSubject keptFieldSubject = mainClassSubject.uniqueFieldWithFinalName("f");
+              assertEquals(
+                  aClassSubject.getFinalName(),
+                  keptFieldSubject.getField().getType().getTypeName());
+
+              FieldSubject optimizedFieldSubject = mainClassSubject.uniqueFieldWithFinalName("f$1");
+              assertEquals(
+                  aClassSubject.getFinalName(),
+                  optimizedFieldSubject.getField().getType().getTypeName());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A");
   }
 
   private static byte[] getTransformedMain() throws IOException {