Tests for field minification in presence of reserved names

The tests verify that names that are explicitly reserved via keep rules are taken into account by the field name minifier, and that field names in the library are also treated as being implicitly reserved.

Bug: 128973195, 127932803, 128656974, 128600647
Change-Id: Ic7560780cafadbbc8543d86dd8628e2866fc508a
diff --git a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java
new file mode 100644
index 0000000..e88aed8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubClassTest.java
@@ -0,0 +1,191 @@
+// Copyright (c) 2019, 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.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Tests that a reserved field name from a given class is taken into account when renaming the
+ * fields in supertypes of that class.
+ */
+@RunWith(Parameterized.class)
+public class ReservedFieldNameInSubClassTest extends TestBase {
+
+  private final boolean reserveName;
+
+  @Parameterized.Parameters(name = "Reserve name: {0}")
+  public static Boolean[] data() {
+    return BooleanUtils.values();
+  }
+
+  public ReservedFieldNameInSubClassTest(boolean reserveName) {
+    this.reserveName = reserveName;
+  }
+
+  @Test
+  public void test() throws Exception {
+    String expectedOutput = StringUtils.lines("Hello world!");
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addProgramClasses(
+                TestClass.class, A.class, B.class, C.class, I.class, J.class, K.class)
+            .enableMergeAnnotations()
+            .addKeepMainRule(TestClass.class)
+            .addKeepRules(
+                reserveName
+                    ? "-keepclassmembernames class "
+                        + C.class.getTypeName()
+                        + "{ java.lang.String a; }"
+                    : "")
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+
+    ClassSubject bClassSubject = inspector.clazz(B.class);
+    assertThat(bClassSubject, isPresent());
+
+    ClassSubject cClassSubject = inspector.clazz(C.class);
+    assertThat(cClassSubject, isPresent());
+
+    ClassSubject iClassSubject = inspector.clazz(I.class);
+    assertThat(iClassSubject, isPresent());
+
+    ClassSubject jClassSubject = inspector.clazz(J.class);
+    assertThat(jClassSubject, isPresent());
+
+    ClassSubject kClassSubject = inspector.clazz(K.class);
+    assertThat(kClassSubject, isPresent());
+
+    FieldSubject f1FieldSubject = aClassSubject.uniqueFieldWithName("f1");
+    assertThat(f1FieldSubject, isPresent());
+
+    FieldSubject f2FieldSubject = bClassSubject.uniqueFieldWithName("f2");
+    assertThat(f2FieldSubject, isPresent());
+
+    FieldSubject f3FieldSubject = iClassSubject.uniqueFieldWithName("f3");
+    assertThat(f3FieldSubject, isPresent());
+
+    FieldSubject f4FieldSubject = jClassSubject.uniqueFieldWithName("f4");
+    assertThat(f4FieldSubject, isPresent());
+
+    FieldSubject f5FieldSubject = kClassSubject.uniqueFieldWithName("f5");
+    assertThat(f5FieldSubject, isPresent());
+
+    FieldSubject aFieldSubject = cClassSubject.uniqueFieldWithName("a");
+    assertThat(aFieldSubject, isPresent());
+
+    if (reserveName) {
+      // TODO(b/128973195): A.f1 should be renamed to e instead of a.
+      assertThat(f1FieldSubject, isRenamed());
+      assertEquals("a", f1FieldSubject.getFinalName());
+
+      // TODO(b/128973195): B.f2 should be renamed to f instead of a.
+      assertThat(f2FieldSubject, isRenamed());
+      assertEquals("a", f2FieldSubject.getFinalName());
+
+      // TODO(b/128973195): I.f3 should be renamed to b instead of a.
+      assertThat(f3FieldSubject, isRenamed());
+      assertEquals("a", f3FieldSubject.getFinalName());
+
+      // TODO(b/128973195): J.f4 should be renamed to c instead of b.
+      assertThat(f4FieldSubject, isRenamed());
+      assertEquals("b", f4FieldSubject.getFinalName());
+
+      // TODO(b/128973195): K.f5 should be renamed to d instead of a.
+      assertThat(f5FieldSubject, isRenamed());
+      assertEquals("a", f5FieldSubject.getFinalName());
+
+      // B.a should not be renamed because it is not allowed to be minified.
+      assertThat(aFieldSubject, not(isRenamed()));
+    } else {
+      // TODO(b/128973195): A.f1 should be renamed to d instead of a.
+      assertThat(f1FieldSubject, isRenamed());
+      assertEquals("a", f1FieldSubject.getFinalName());
+
+      // TODO(b/128973195): B.f2 should be renamed to e instead of b.
+      assertThat(f2FieldSubject, isRenamed());
+      assertEquals("b", f2FieldSubject.getFinalName());
+
+      // TODO(b/128973195): I.f3 should be renamed to a instead of a.
+      assertThat(f3FieldSubject, isRenamed());
+      assertEquals("a", f3FieldSubject.getFinalName());
+
+      assertThat(f4FieldSubject, isRenamed());
+      assertEquals("b", f4FieldSubject.getFinalName());
+
+      // TODO(b/128973195): K.f5 should be renamed to c instead of a.
+      assertThat(f5FieldSubject, isRenamed());
+      assertEquals("a", f5FieldSubject.getFinalName());
+
+      // TODO(b/128973195): C.a should be renamed to f instead of c.
+      assertThat(aFieldSubject, isRenamed());
+      assertEquals("c", aFieldSubject.getFinalName());
+    }
+  }
+
+  @NeverMerge
+  static class A {
+
+    String f1 = "He";
+  }
+
+  @NeverMerge
+  static class B extends A {
+
+    String f2 = "l";
+  }
+
+  @NeverMerge
+  interface I {
+
+    String f3 = System.currentTimeMillis() >= 0 ? "lo" : null;
+  }
+
+  @NeverMerge
+  interface J extends I {
+
+    String f4 = System.currentTimeMillis() >= 0 ? " " : null;
+  }
+
+  @NeverMerge
+  interface K {
+
+    String f5 = System.currentTimeMillis() >= 0 ? "world" : null;
+  }
+
+  static class C extends B implements J, K {
+
+    String a = "!";
+
+    @Override
+    public String toString() {
+      return f1 + f2 + f3 + f4 + f5 + a;
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new C());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java
new file mode 100644
index 0000000..26465b6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSubInterfaceTest.java
@@ -0,0 +1,158 @@
+// Copyright (c) 2019, 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.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Tests that a reserved field name from an interface I, which is implemented by a subclass B, is
+ * taken into account when renaming the fields in a superclass of B.
+ */
+@RunWith(Parameterized.class)
+public class ReservedFieldNameInSubInterfaceTest extends TestBase {
+
+  private final boolean reserveName;
+
+  @Parameterized.Parameters(name = "Reserve name: {0}")
+  public static Boolean[] data() {
+    return BooleanUtils.values();
+  }
+
+  public ReservedFieldNameInSubInterfaceTest(boolean reserveName) {
+    this.reserveName = reserveName;
+  }
+
+  @Test
+  public void testProgramField() throws Exception {
+    String expectedOutput = StringUtils.lines("Hello world!");
+    R8TestRunResult result =
+        testForR8(Backend.DEX)
+            .addProgramClasses(TestClass.class, A.class, B.class, I.class, J.class)
+            .enableMergeAnnotations()
+            .addKeepMainRule(TestClass.class)
+            .addKeepRules(
+                reserveName
+                    ? "-keepclassmembernames class "
+                        + J.class.getTypeName()
+                        + "{ java.lang.String a; }"
+                    : "")
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput);
+
+    CodeInspector inspector = result.inspector();
+
+    ClassSubject iClassSubject = inspector.clazz(I.class);
+    assertThat(iClassSubject, isPresent());
+
+    ClassSubject jClassSubject = inspector.clazz(J.class);
+    assertThat(jClassSubject, isPresent());
+
+    FieldSubject f1FieldSubject = iClassSubject.uniqueFieldWithName("f1");
+    assertThat(f1FieldSubject, isPresent());
+    assertThat(f1FieldSubject, isRenamed());
+
+    FieldSubject aFieldSubject = jClassSubject.uniqueFieldWithName("a");
+    assertThat(aFieldSubject, isPresent());
+
+    if (reserveName) {
+      assertThat(aFieldSubject, not(isRenamed()));
+
+      // Interface fields are visited/renamed before fields on classes. Thus, the interface field
+      // I.f1 will be visited first and assigned the name b (since the name a is reserved).
+      assertEquals("b", f1FieldSubject.getFinalName());
+
+    } else {
+      // Interface fields are visited/renamed before fields on classes. Thus, the interface field
+      // I.f1 will be visited first and assigned the name a.
+      assertEquals("a", f1FieldSubject.getFinalName());
+
+      // The interface field J.a will be visited after I.f1, and will therefore be assigned the name
+      // b.
+      assertThat(aFieldSubject, isRenamed());
+      assertEquals("b", aFieldSubject.getFinalName());
+    }
+
+    inspect(inspector);
+  }
+
+  @Test
+  public void testLibraryField() throws Exception {
+    assumeFalse("No need to add keep rules for the library", reserveName);
+
+    String expectedOutput = StringUtils.lines("Hello world!");
+    testForR8(Backend.DEX)
+        .addProgramClasses(TestClass.class, A.class, B.class)
+        .addLibraryClasses(I.class, J.class)
+        .enableMergeAnnotations()
+        .addKeepMainRule(TestClass.class)
+        .compile()
+        .addRunClasspathFiles(
+            testForD8().addProgramClasses(I.class, J.class).compile().writeToZip())
+        .run(TestClass.class)
+        .assertSuccessWithOutput(expectedOutput)
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+
+    FieldSubject f2FieldSubject = aClassSubject.uniqueFieldWithName("f2");
+    assertThat(f2FieldSubject, isPresent());
+    assertThat(f2FieldSubject, isRenamed());
+
+    // TODO(b/128973195): A.f2 should be renamed to c.
+    assertEquals("a", f2FieldSubject.getFinalName());
+  }
+
+  @NeverMerge
+  interface I {
+
+    String f1 = System.currentTimeMillis() >= 0 ? "Hello" : null;
+  }
+
+  @NeverMerge
+  interface J extends I {
+
+    String a = System.currentTimeMillis() >= 0 ? "world!" : null;
+  }
+
+  @NeverMerge
+  static class A {
+
+    String f2 = " ";
+  }
+
+  static class B extends A implements J {
+
+    @Override
+    public String toString() {
+      return f1 + f2 + a;
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new B());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperClassTest.java b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperClassTest.java
new file mode 100644
index 0000000..ce47daf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperClassTest.java
@@ -0,0 +1,155 @@
+// Copyright (c) 2019, 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.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Tests that a reserved field name from a given class A is taken into account when renaming the
+ * fields in subclasses of A.
+ */
+@RunWith(Parameterized.class)
+public class ReservedFieldNameInSuperClassTest extends TestBase {
+
+  private final boolean reserveName;
+
+  @Parameterized.Parameters(name = "Reserve name: {0}")
+  public static Boolean[] data() {
+    return BooleanUtils.values();
+  }
+
+  public ReservedFieldNameInSuperClassTest(boolean reserveName) {
+    this.reserveName = reserveName;
+  }
+
+  @Test
+  public void testProgramClass() throws Exception {
+    String expectedOutput = StringUtils.lines("A.a", "ASub1.foo", "ASub2.bar");
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addProgramClasses(A.class, ASub1.class, ASub2.class, TestClass.class)
+            .enableClassInliningAnnotations()
+            .addKeepMainRule(TestClass.class)
+            .addKeepRules(
+                reserveName
+                    ? "-keepclassmembernames class " + A.class.getTypeName() + "{ <fields>; }"
+                    : "")
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject testClassForFieldSubject = inspector.clazz(A.class);
+    assertThat(testClassForFieldSubject, isPresent());
+
+    FieldSubject aFieldSubject = testClassForFieldSubject.uniqueFieldWithName("a");
+    assertThat(aFieldSubject, isPresent());
+
+    // Fields are visited/renamed according to the class hierarchy order. Thus, the field A.a will
+    // be visited first and assigned the name a. As it ends up receiving the same name as in the
+    // original program, it has not technically been renamed.
+    assertThat(aFieldSubject, not(isRenamed()));
+
+    inspect(inspector);
+  }
+
+  @Test
+  public void testLibraryClass() throws Exception {
+    assumeFalse("No need to add keep rules for the library", reserveName);
+
+    String expectedOutput = StringUtils.lines("A.a", "ASub1.foo", "ASub2.bar");
+    testForR8(Backend.DEX)
+        .addProgramClasses(ASub1.class, ASub2.class, TestClass.class)
+        .addLibraryClasses(A.class)
+        .enableClassInliningAnnotations()
+        .addKeepMainRule(TestClass.class)
+        .compile()
+        .addRunClasspathFiles(testForD8().addProgramClasses(A.class).compile().writeToZip())
+        .run(TestClass.class)
+        .assertSuccessWithOutput(expectedOutput)
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject aSub1ClassSubject = inspector.clazz(ASub1.class);
+    assertThat(aSub1ClassSubject, isPresent());
+
+    FieldSubject fooFieldSubject = aSub1ClassSubject.uniqueFieldWithName("foo");
+    assertThat(fooFieldSubject, isPresent());
+    assertThat(fooFieldSubject, isRenamed());
+    assertNotEquals("a", fooFieldSubject.getFinalName());
+
+    ClassSubject aSub2ClassSubject = inspector.clazz(ASub2.class);
+    assertThat(aSub2ClassSubject, isPresent());
+
+    FieldSubject barFieldSubject = aSub2ClassSubject.uniqueFieldWithName("bar");
+    assertThat(barFieldSubject, isPresent());
+    assertThat(barFieldSubject, isRenamed());
+    assertNotEquals("a", barFieldSubject.getFinalName());
+
+    // Verify that ASub1.foo and ASub2.bar has been renamed to the same name.
+    //
+    // Note that this is not a requirement for the correctness of the field name minifier. The field
+    // name minifier is in principle free to choose different names for ASub1.foo and ASub2.bar, but
+    // this would have a negative impact on code size.
+    assertEquals(fooFieldSubject.getFinalName(), barFieldSubject.getFinalName());
+  }
+
+  static class A {
+
+    static String a;
+  }
+
+  static class ASub1 extends A {
+
+    static String foo;
+
+    ASub1() {
+      a = "A.a";
+      foo = "ASub1.foo";
+    }
+
+    @Override
+    public String toString() {
+      return a + System.lineSeparator() + foo;
+    }
+  }
+
+  static class ASub2 extends A {
+
+    static String bar;
+
+    ASub2() {
+      bar = "ASub2.bar";
+    }
+
+    @Override
+    public String toString() {
+      return bar;
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new ASub1());
+      System.out.println(new ASub2());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperInterfaceTest.java b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperInterfaceTest.java
new file mode 100644
index 0000000..71a1b57
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/ReservedFieldNameInSuperInterfaceTest.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2019, 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.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Tests that a reserved field name from a given interface I is taken into account when renaming the
+ * fields in subclasses of I.
+ */
+@RunWith(Parameterized.class)
+public class ReservedFieldNameInSuperInterfaceTest extends TestBase {
+
+  private final boolean reserveName;
+
+  @Parameterized.Parameters(name = "Reserve name: {0}")
+  public static Boolean[] data() {
+    return BooleanUtils.values();
+  }
+
+  public ReservedFieldNameInSuperInterfaceTest(boolean reserveName) {
+    this.reserveName = reserveName;
+  }
+
+  @Test
+  public void testProgramField() throws Exception {
+    String expectedOutput = StringUtils.lines("Hello world!");
+    R8TestRunResult result =
+        testForR8(Backend.DEX)
+            .addProgramClasses(TestClass.class, A.class, I.class, J.class)
+            .addKeepMainRule(TestClass.class)
+            .addKeepRules(
+                reserveName
+                    ? "-keepclassmembernames class " + I.class.getTypeName() + "{ <fields>; }"
+                    : "")
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput);
+
+    CodeInspector inspector = result.inspector();
+
+    ClassSubject iClassSubject = inspector.clazz(I.class);
+    assertThat(iClassSubject, isPresent());
+
+    FieldSubject aFieldSubject = iClassSubject.uniqueFieldWithName("a");
+    assertThat(aFieldSubject, isPresent());
+
+    // Interface fields are visited/renamed before fields on classes. Thus, the interface field I.a
+    // will be visited first and assigned the name a. As it ends up receiving the same name as in
+    // the input program, it has not technically been renamed.
+    assertThat(aFieldSubject, not(isRenamed()));
+
+    inspect(inspector);
+  }
+
+  @Test
+  public void testLibraryField() throws Exception {
+    assumeFalse("No need to add keep rules for the library", reserveName);
+
+    String expectedOutput = StringUtils.lines("Hello world!");
+    testForR8(Backend.DEX)
+        .addProgramClasses(TestClass.class, A.class, J.class)
+        .addLibraryClasses(I.class)
+        .enableMemberValuePropagationAnnotations()
+        .enableMergeAnnotations()
+        .addKeepMainRule(TestClass.class)
+        .compile()
+        .addRunClasspathFiles(testForD8().addProgramClasses(I.class).compile().writeToZip())
+        .run(TestClass.class)
+        .assertSuccessWithOutput(expectedOutput)
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject jClassSubject = inspector.clazz(J.class);
+    assertThat(jClassSubject, isPresent());
+
+    FieldSubject f1FieldSubject = jClassSubject.uniqueFieldWithName("f1");
+    assertThat(f1FieldSubject, isPresent());
+    assertThat(f1FieldSubject, isRenamed());
+    assertEquals("b", f1FieldSubject.getFinalName());
+
+    ClassSubject aClassSubject = inspector.clazz(A.class);
+    assertThat(aClassSubject, isPresent());
+
+    FieldSubject f2FieldSubject = aClassSubject.uniqueFieldWithName("f2");
+    assertThat(f2FieldSubject, isPresent());
+    assertThat(f2FieldSubject, isRenamed());
+
+    // TODO(b/128973195): A.f2 should be renamed to c instead of a.
+    assertEquals("a", f2FieldSubject.getFinalName());
+  }
+
+  interface I {
+
+    String a = System.currentTimeMillis() >= 0 ? "Hello" : null;
+  }
+
+  interface J extends I {
+
+    String f1 = System.currentTimeMillis() >= 0 ? " " : null;
+  }
+
+  static class A implements I {
+
+    String f2 = "world!";
+
+    @Override
+    public String toString() {
+      return a + J.f1 + f2;
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println(new A());
+    }
+  }
+}