Make FieldAccessInfo and FieldAccessInfoCollection immutable

Optimizations that need to update FieldAccessInfo get access to a new MutableFieldAccessInfoCollection and MutableFieldAccessInfo.

No optimizations other than the Enqueuer should have access to the underlying implementation, FieldAccessInfoCollectionImpl and FieldAccessInfoImpl.

Change-Id: I36a4e395ed2a32b170294a9c6b6e20976e9634b9
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 78c7d8c..c9be529 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -506,10 +506,12 @@
 
       if (options.getTestingOptions().enableMemberRebindingAnalysis) {
         new MemberRebindingAnalysis(appViewWithLiveness).run(executorService);
-      } else {
-        appViewWithLiveness.appInfo().getFieldAccessInfoCollection().flattenAccessContexts();
       }
-      appViewWithLiveness.appInfo().notifyMemberRebindingFinished(appViewWithLiveness);
+      appViewWithLiveness.appInfo().getMutableFieldAccessInfoCollection().flattenAccessContexts();
+      appViewWithLiveness
+          .appInfo()
+          .getMutableFieldAccessInfoCollection()
+          .restrictToProgram(appView);
 
       assert ArtProfileCompletenessChecker.verify(appView);
 
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
index 126a0d0..357f02b 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfo.java
@@ -4,22 +4,17 @@
 
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ir.analysis.proto.ProtoReferences;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /** Provides immutable access to {@link FieldAccessInfoImpl}. */
 public interface FieldAccessInfo {
 
-  FieldAccessInfoImpl asMutable();
-
   DexField getField();
 
   int getNumberOfWriteContexts();
 
-  AbstractAccessContexts getReadsWithContexts();
-
-  AbstractAccessContexts getWritesWithContexts();
-
   boolean hasKnownReadContexts();
 
   boolean hasKnownWriteContexts();
@@ -32,8 +27,6 @@
 
   boolean hasReflectiveAccess();
 
-  boolean hasReflectiveRead();
-
   boolean hasReflectiveWrite();
 
   default boolean isAccessedFromMethodHandle() {
@@ -48,14 +41,12 @@
 
   boolean isReadFromMethodHandle();
 
-  boolean isReadOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate);
+  boolean isReadOnlyInFindLiteExtensionByNumberMethod(ProtoReferences references);
 
   boolean isWritten();
 
   boolean isWrittenFromMethodHandle();
 
-  boolean isWrittenInMethodSatisfying(Predicate<ProgramMethod> predicate);
-
   boolean isWrittenOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate);
 
   boolean isWrittenOutside(DexEncodedMethod method);
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
index b3f9c25..86394be 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollection.java
@@ -4,23 +4,16 @@
 
 package com.android.tools.r8.graph;
 
-import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
 /** Provides immutable access to {@link FieldAccessInfoCollectionImpl}. */
 public interface FieldAccessInfoCollection<T extends FieldAccessInfo> {
 
-  void destroyAccessContexts();
-
-  void flattenAccessContexts();
-
-  boolean contains(DexField field);
+  default boolean contains(DexField field) {
+    return get(field) != null;
+  }
 
   T get(DexField field);
 
   void forEach(Consumer<T> consumer);
-
-  void removeIf(BiPredicate<DexField, FieldAccessInfoImpl> predicate);
-
-  void restrictToProgram(DexDefinitionSupplier definitions);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
index 24b9d33..92ac34d 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
@@ -17,7 +17,8 @@
 import java.util.function.Function;
 
 public class FieldAccessInfoCollectionImpl
-    implements FieldAccessInfoCollection<FieldAccessInfoImpl> {
+    implements MutableFieldAccessInfoCollection<
+        FieldAccessInfoCollectionImpl, FieldAccessInfoImpl> {
 
   private final Map<DexField, FieldAccessInfoImpl> infos;
 
@@ -45,15 +46,11 @@
   }
 
   @Override
-  public boolean contains(DexField field) {
-    return infos.containsKey(field);
-  }
-
-  @Override
   public FieldAccessInfoImpl get(DexField field) {
     return infos.get(field);
   }
 
+  @Override
   public FieldAccessInfoImpl extend(DexField field, FieldAccessInfoImpl info) {
     assert !infos.containsKey(field);
     infos.put(field, info);
@@ -81,6 +78,7 @@
     removeIf((field, info) -> !definitions.definitionForHolder(field).isProgramClass());
   }
 
+  @Override
   public FieldAccessInfoCollectionImpl rewrittenWithLens(
       DexDefinitionSupplier definitions, GraphLens lens, Timing timing) {
     timing.begin("Rewrite FieldAccessInfoCollectionImpl");
@@ -105,6 +103,7 @@
     return true;
   }
 
+  @Override
   public FieldAccessInfoCollectionImpl withoutPrunedItems(PrunedItems prunedItems) {
     Iterator<Entry<DexField, FieldAccessInfoImpl>> iterator = infos.entrySet().iterator();
     while (iterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index a09751b..31d875c 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AbstractAccessContexts.ConcreteAccessContexts;
 import com.android.tools.r8.graph.lens.GraphLens;
+import com.android.tools.r8.ir.analysis.proto.ProtoReferences;
 import com.android.tools.r8.shaking.Enqueuer.FieldAccessKind;
 import com.android.tools.r8.utils.timing.Timing;
 import com.google.common.collect.Sets;
@@ -19,7 +20,7 @@
  *
  * <p>The information is generated by the {@link com.android.tools.r8.shaking.Enqueuer}.
  */
-public class FieldAccessInfoImpl implements FieldAccessInfo {
+public class FieldAccessInfoImpl implements MutableFieldAccessInfo {
 
   public static final FieldAccessInfoImpl MISSING_FIELD_ACCESS_INFO = new FieldAccessInfoImpl(null);
 
@@ -74,16 +75,10 @@
   }
 
   @Override
-  public FieldAccessInfoImpl asMutable() {
-    return this;
-  }
-
-  @Override
   public DexField getField() {
     return field;
   }
 
-  @Override
   public AbstractAccessContexts getReadsWithContexts() {
     return readsWithContexts;
   }
@@ -92,7 +87,6 @@
     this.readsWithContexts = readsWithContexts;
   }
 
-  @Override
   public AbstractAccessContexts getWritesWithContexts() {
     return writesWithContexts;
   }
@@ -162,13 +156,16 @@
     return hasReflectiveRead() || hasReflectiveWrite();
   }
 
-  @Override
   public boolean hasReflectiveRead() {
     return (flags & FLAG_HAS_REFLECTIVE_READ) != 0;
   }
 
-  public void setHasReflectiveRead() {
-    flags |= FLAG_HAS_REFLECTIVE_READ;
+  public boolean setHasReflectiveRead() {
+    if (!hasReflectiveRead()) {
+      flags |= FLAG_HAS_REFLECTIVE_READ;
+      return true;
+    }
+    return false;
   }
 
   @Override
@@ -176,8 +173,12 @@
     return (flags & FLAG_HAS_REFLECTIVE_WRITE) != 0;
   }
 
-  public void setHasReflectiveWrite() {
-    flags |= FLAG_HAS_REFLECTIVE_WRITE;
+  public boolean setHasReflectiveWrite() {
+    if (!hasReflectiveWrite()) {
+      flags |= FLAG_HAS_REFLECTIVE_WRITE;
+      return true;
+    }
+    return false;
   }
 
   /** Returns true if this field is read by the program. */
@@ -236,12 +237,11 @@
     flags &= ~FLAG_IS_READ_FROM_RECORD_INVOKE_DYNAMIC;
   }
 
-  /**
-   * Returns true if this field is only read by methods for which {@param predicate} returns true.
-   */
   @Override
-  public boolean isReadOnlyInMethodSatisfying(Predicate<ProgramMethod> predicate) {
-    return readsWithContexts.isAccessedOnlyInMethodSatisfying(predicate) && !isReadIndirectly();
+  public boolean isReadOnlyInFindLiteExtensionByNumberMethod(ProtoReferences references) {
+    return !isReadIndirectly()
+        && readsWithContexts.isAccessedOnlyInMethodSatisfying(
+            references::isFindLiteExtensionByNumberMethod);
   }
 
   /** Returns true if this field is written by the program. */
@@ -270,7 +270,6 @@
   /**
    * Returns true if this field is written by a method for which {@param predicate} returns true.
    */
-  @Override
   public boolean isWrittenInMethodSatisfying(Predicate<ProgramMethod> predicate) {
     return writesWithContexts.isAccessedInMethodSatisfying(predicate);
   }
@@ -312,6 +311,7 @@
     return false;
   }
 
+  @Override
   public void clearReads() {
     assert !hasReflectiveAccess();
     assert !isReadFromAnnotation();
@@ -320,6 +320,7 @@
     clearReadFromRecordInvokeDynamic();
   }
 
+  @Override
   public void clearWrites() {
     writesWithContexts = AbstractAccessContexts.empty();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/MutableFieldAccessInfo.java b/src/main/java/com/android/tools/r8/graph/MutableFieldAccessInfo.java
new file mode 100644
index 0000000..ee1d85b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/MutableFieldAccessInfo.java
@@ -0,0 +1,11 @@
+// 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;
+
+public interface MutableFieldAccessInfo extends FieldAccessInfo {
+
+  void clearReads();
+
+  void clearWrites();
+}
diff --git a/src/main/java/com/android/tools/r8/graph/MutableFieldAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/MutableFieldAccessInfoCollection.java
new file mode 100644
index 0000000..17a8c35
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/MutableFieldAccessInfoCollection.java
@@ -0,0 +1,27 @@
+// 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.graph.lens.GraphLens;
+import com.android.tools.r8.utils.timing.Timing;
+import java.util.function.BiPredicate;
+
+public interface MutableFieldAccessInfoCollection<
+        S extends MutableFieldAccessInfoCollection<S, T>, T extends MutableFieldAccessInfo>
+    extends FieldAccessInfoCollection<T> {
+
+  void destroyAccessContexts();
+
+  T extend(DexField field, FieldAccessInfoImpl info);
+
+  void flattenAccessContexts();
+
+  void removeIf(BiPredicate<DexField, FieldAccessInfoImpl> predicate);
+
+  void restrictToProgram(DexDefinitionSupplier definitions);
+
+  S rewrittenWithLens(DexDefinitionSupplier definitions, GraphLens lens, Timing timing);
+
+  S withoutPrunedItems(PrunedItems prunedItems);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
index 8138996..1279d85 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/TrivialFieldAccessReprocessor.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
 import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.MutableFieldAccessInfoCollection;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.bytecodemetadata.BytecodeInstructionMetadata;
@@ -169,15 +170,16 @@
   }
 
   private void clearReadsAndWritesFromFieldsOfInterest(AppInfoWithLiveness appInfo) {
-    FieldAccessInfoCollection<?> fieldAccessInfoCollection = appInfo.getFieldAccessInfoCollection();
+    MutableFieldAccessInfoCollection<?, ?> fieldAccessInfoCollection =
+        appInfo.getMutableFieldAccessInfoCollection();
     for (DexEncodedField field : constantFields) {
-      fieldAccessInfoCollection.get(field.getReference()).asMutable().clearReads();
+      fieldAccessInfoCollection.get(field.getReference()).clearReads();
     }
     for (DexEncodedField field : readFields.keySet()) {
-      fieldAccessInfoCollection.get(field.getReference()).asMutable().clearWrites();
+      fieldAccessInfoCollection.get(field.getReference()).clearWrites();
     }
     for (DexEncodedField field : writtenFields.keySet()) {
-      fieldAccessInfoCollection.get(field.getReference()).asMutable().clearReads();
+      fieldAccessInfoCollection.get(field.getReference()).clearReads();
     }
   }
 
@@ -242,7 +244,8 @@
   }
 
   private void processFieldsNeverRead(AppInfoWithLiveness appInfo) {
-    FieldAccessInfoCollection<?> fieldAccessInfoCollection = appInfo.getFieldAccessInfoCollection();
+    MutableFieldAccessInfoCollection<?, ?> fieldAccessInfoCollection =
+        appInfo.getMutableFieldAccessInfoCollection();
     writtenFields
         .entrySet()
         .removeIf(
@@ -253,7 +256,7 @@
     writtenFields.forEach(
         (field, contexts) -> {
           assert !readFields.containsKey(field);
-          fieldAccessInfoCollection.get(field.getReference()).asMutable().clearReads();
+          fieldAccessInfoCollection.get(field.getReference()).clearReads();
           methodsToReprocess.addAll(
               contexts.asConcrete().getAccessesWithContexts().values().iterator().next());
           methodsToReprocess.addAll(dependencies.getOrDefault(field, ProgramMethodSet.empty()));
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index ae7191d..45afec6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -103,7 +103,10 @@
    */
   public TreePrunerConfiguration run(Enqueuer.Mode mode) {
     forEachDeadProtoExtensionField(field -> recordDeadProtoExtensionField(field, mode));
-    appView.appInfo().getFieldAccessInfoCollection().removeIf((field, info) -> wasRemoved(field));
+    appView
+        .appInfo()
+        .getMutableFieldAccessInfoCollection()
+        .removeIf((field, info) -> wasRemoved(field));
     return createTreePrunerConfiguration(mode);
   }
 
@@ -274,8 +277,7 @@
     }
 
     // Multiple GeneratedExtensionRegistries exist in Chrome; 1 per feature split.
-    return fieldAccessInfo.isReadOnlyInMethodSatisfying(
-        references::isFindLiteExtensionByNumberMethod);
+    return fieldAccessInfo.isReadOnlyInFindLiteExtensionByNumberMethod(references);
   }
 
   private void forEachDeadProtoExtensionField(Consumer<DexField> consumer) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
index 7300dd7..29bce4e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryR8IRConverter.java
@@ -116,7 +116,7 @@
     new IdentifierMinifier(appView).rewriteDexItemBasedConstStringInStaticFields(executorService);
 
     // The field access info collection is not maintained during IR processing.
-    appView.appInfo().withLiveness().getFieldAccessInfoCollection().destroyAccessContexts();
+    appView.appInfoWithLiveness().getMutableFieldAccessInfoCollection().destroyAccessContexts();
 
     // Assure that no more optimization feedback left after primary processing.
     assert feedback.noUpdatesLeft();
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index f16d405..b20df20 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -360,11 +360,9 @@
   }
 
   public void run(ExecutorService executorService) throws ExecutionException {
-    AppInfoWithLiveness appInfo = appView.appInfo();
     Map<InvokeType, NonReboundMethodAccessCollection> nonReboundMethodAccessCollections =
         computeNonReboundMethodAccessCollections(executorService);
     computeMethodRebinding(nonReboundMethodAccessCollections);
-    appInfo.getFieldAccessInfoCollection().flattenAccessContexts();
     MemberRebindingLens memberRebindingLens = lensBuilder.build();
     appView.setGraphLens(memberRebindingLens);
     eventConsumer.finished(appView, memberRebindingLens);
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 3d5de3b..577626e 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -33,13 +33,14 @@
 import com.android.tools.r8.graph.DispatchTargetLookupResult;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollection;
-import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.InstantiatedSubTypeInfo;
 import com.android.tools.r8.graph.LookupMethodTarget;
 import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
 import com.android.tools.r8.graph.LookupTarget;
 import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.MutableFieldAccessInfo;
+import com.android.tools.r8.graph.MutableFieldAccessInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
 import com.android.tools.r8.graph.ProgramField;
@@ -132,7 +133,8 @@
    * a given field is read/written by the program, and it also includes all indirect accesses to
    * each field. The latter is used, for example, during member rebinding.
    */
-  private final FieldAccessInfoCollectionImpl fieldAccessInfoCollection;
+  private final MutableFieldAccessInfoCollection<?, ? extends MutableFieldAccessInfo>
+      fieldAccessInfoCollection;
 
   /** Information about instantiated classes and their allocation sites. */
   private final ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection;
@@ -202,7 +204,8 @@
       Set<DexMethod> bootstrapMethods,
       Set<DexMethod> virtualMethodsTargetedByInvokeDirect,
       Set<DexMethod> liveMethods,
-      FieldAccessInfoCollectionImpl fieldAccessInfoCollection,
+      MutableFieldAccessInfoCollection<?, ? extends MutableFieldAccessInfo>
+          fieldAccessInfoCollection,
       ObjectAllocationInfoCollectionImpl objectAllocationInfoCollection,
       Map<DexCallSite, ProgramMethodSet> callSites,
       KeepInfoCollection keepInfo,
@@ -401,10 +404,6 @@
     return map;
   }
 
-  public void notifyMemberRebindingFinished(AppView<AppInfoWithLiveness> appView) {
-    getFieldAccessInfoCollection().restrictToProgram(appView);
-  }
-
   @Override
   public void notifyMinifierFinished() {
     liveMethods = ThrowingSet.get();
@@ -745,7 +744,8 @@
     return fieldAccessInfoCollection;
   }
 
-  FieldAccessInfoCollectionImpl getMutableFieldAccessInfoCollection() {
+  public MutableFieldAccessInfoCollection<?, ? extends MutableFieldAccessInfo>
+      getMutableFieldAccessInfoCollection() {
     return fieldAccessInfoCollection;
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
index bf91868..305d864 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLivenessModifier.java
@@ -6,8 +6,8 @@
 
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
-import com.android.tools.r8.graph.FieldAccessInfoImpl;
+import com.android.tools.r8.graph.MutableFieldAccessInfo;
+import com.android.tools.r8.graph.MutableFieldAccessInfoCollection;
 import com.android.tools.r8.utils.SetUtils;
 import java.util.Set;
 
@@ -37,11 +37,11 @@
     appInfo.mutateObjectAllocationInfoCollection(
         mutator -> noLongerInstantiatedClasses.forEach(mutator::markNoLongerInstantiated));
     // Written fields.
-    FieldAccessInfoCollectionImpl fieldAccessInfoCollection =
-        appInfo.getMutableFieldAccessInfoCollection();
+    MutableFieldAccessInfoCollection<?, ? extends MutableFieldAccessInfo>
+        fieldAccessInfoCollection = appInfo.getMutableFieldAccessInfoCollection();
     noLongerWrittenFields.forEach(
         field -> {
-          FieldAccessInfoImpl fieldAccessInfo = fieldAccessInfoCollection.get(field);
+          MutableFieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field);
           if (fieldAccessInfo != null) {
             fieldAccessInfo.clearWrites();
           }
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 ca00c0c..5e9c0fd 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1228,20 +1228,10 @@
       return false;
     }
     if (isReflective) {
-      if (isRead) {
-        if (!info.hasReflectiveRead()) {
-          info.setHasReflectiveRead();
-          return true;
-        }
-      } else {
-        if (!info.hasReflectiveWrite()) {
-          info.setHasReflectiveWrite();
-          return true;
-        }
-      }
-      return false;
+      return isRead ? info.setHasReflectiveRead() : info.setHasReflectiveWrite();
+    } else {
+      return isRead ? info.recordRead(field, context) : info.recordWrite(field, context);
     }
-    return isRead ? info.recordRead(field, context) : info.recordWrite(field, context);
   }
 
   void traceCallSite(
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 6a0cf10..3cbf5ae 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessInfo;
 import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
+import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -162,7 +163,7 @@
     assert enqueuer.getKeepInfo(field).isBottom();
     assert !enqueuer.getKeepInfo(field).isPinned(options) || options.isOptimizedResourceShrinking();
 
-    FieldAccessInfo info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
+    FieldAccessInfoImpl info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
     if (info.hasReflectiveAccess()
         || info.isAccessedFromMethodHandle()
         || info.isReadFromAnnotation()
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index eda1979..5ff3b75 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessInfo;
+import com.android.tools.r8.graph.FieldAccessInfoImpl;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
@@ -843,7 +844,7 @@
 
     @Override
     public void enqueueTraceReflectiveFieldAccessAction(ProgramField field, ProgramMethod context) {
-      FieldAccessInfo info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
+      FieldAccessInfoImpl info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
       if (info == null || !info.hasReflectiveRead() || !info.hasReflectiveWrite()) {
         queue.add(new TraceReflectiveFieldAccessAction(field, context));
       }
@@ -851,7 +852,7 @@
 
     @Override
     public void enqueueTraceReflectiveFieldReadAction(ProgramField field, ProgramMethod context) {
-      FieldAccessInfo info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
+      FieldAccessInfoImpl info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
       if (info == null || !info.hasReflectiveRead()) {
         queue.add(
             new TraceReflectiveFieldAccessAction(
diff --git a/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java b/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java
index a05a33b..2dadd07 100644
--- a/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java
+++ b/src/main/java/com/android/tools/r8/shaking/FieldAccessInfoCollectionModifier.java
@@ -8,8 +8,9 @@
 import com.android.tools.r8.graph.AbstractAccessContexts.ConcreteAccessContexts;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
 import com.android.tools.r8.graph.FieldAccessInfoImpl;
+import com.android.tools.r8.graph.MutableFieldAccessInfo;
+import com.android.tools.r8.graph.MutableFieldAccessInfoCollection;
 import com.android.tools.r8.graph.ProgramMethod;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -66,13 +67,14 @@
   }
 
   public void modify(AppView<AppInfoWithLiveness> appView) {
-    FieldAccessInfoCollectionImpl impl = appView.appInfo().getMutableFieldAccessInfoCollection();
+    MutableFieldAccessInfoCollection<?, ? extends MutableFieldAccessInfo>
+        mutableFieldAccessInfoCollection = appView.appInfo().getMutableFieldAccessInfoCollection();
     newFieldAccessContexts.forEach(
         (field, accessContexts) -> {
           FieldAccessInfoImpl fieldAccessInfo = new FieldAccessInfoImpl(field);
           fieldAccessInfo.setReadsWithContexts(accessContexts.readsWithContexts);
           fieldAccessInfo.setWritesWithContexts(accessContexts.writesWithContexts);
-          impl.extend(field, fieldAccessInfo);
+          mutableFieldAccessInfoCollection.extend(field, fieldAccessInfo);
         });
   }