Remove all field states

Bug: b/296030319
Change-Id: I9f80ac358a561a4d8f7806454f30b88393d96116
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index de3e876..2c917645 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -18,10 +18,6 @@
 import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteArrayTypeFieldState;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteClassTypeFieldState;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcretePrimitiveTypeFieldState;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.FieldState;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.DynamicTypeWithUpperBound;
@@ -40,6 +36,10 @@
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldArgumentInitializationInfo;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteArrayTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteClassTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
+import com.android.tools.r8.optimize.argumentpropagation.codescanner.ValueState;
 import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepFieldInfo;
@@ -74,7 +74,7 @@
 
   // Information about the fields in the program. If a field is not a key in the map then no writes
   // has been seen to the field.
-  private final Map<DexEncodedField, FieldState> fieldStates = new ConcurrentHashMap<>();
+  private final Map<DexEncodedField, ValueState> fieldStates = new ConcurrentHashMap<>();
 
   private final Map<DexProgramClass, ProgramFieldMap<AbstractValue>>
       abstractFinalInstanceFieldValues = new ConcurrentHashMap<>();
@@ -141,7 +141,7 @@
             KeepFieldInfo keepInfo = appView.getKeepInfo(field);
             if (keepInfo.isPinned(appView.options())
                 || (accessInfo != null && accessInfo.isWrittenFromMethodHandle())) {
-              fieldStates.put(field.getDefinition(), FieldState.unknown());
+              fieldStates.put(field.getDefinition(), ValueState.unknown());
             }
           });
     }
@@ -166,7 +166,7 @@
                     assert abstractValue.isSingleStringValue()
                         || abstractValue.isSingleDexItemBasedStringValue();
                     if (fieldType == dexItemFactory.stringType) {
-                      return ConcreteClassTypeFieldState.create(
+                      return ConcreteClassTypeValueState.create(
                           abstractValue, DynamicType.definitelyNotNull());
                     } else {
                       ClassTypeElement nonNullableStringType =
@@ -174,16 +174,16 @@
                               .stringType
                               .toTypeElement(appView, definitelyNotNull())
                               .asClassType();
-                      return ConcreteClassTypeFieldState.create(
+                      return ConcreteClassTypeValueState.create(
                           abstractValue, DynamicType.createExact(nonNullableStringType));
                     }
                   } else {
                     assert fieldType.isPrimitiveType();
-                    return ConcretePrimitiveTypeFieldState.create(abstractValue);
+                    return ConcretePrimitiveTypeValueState.create(abstractValue);
                   }
                 }
                 // If the field is already assigned outside the class initializer then just give up.
-                return FieldState.unknown();
+                return ValueState.unknown();
               });
         });
   }
@@ -202,16 +202,17 @@
         value.isZero()
             ? abstractValueFactory.createDefaultValue(field.getType())
             : AbstractValue.unknown();
+    Nullability nullability = value.getType().nullability();
     fieldStates.compute(
         field.getDefinition(),
         (f, fieldState) -> {
           if (fieldState == null || fieldState.isBottom()) {
             DexType fieldType = field.getType();
             if (fieldType.isArrayType()) {
-              return ConcreteArrayTypeFieldState.create(abstractValue);
+              return ConcreteArrayTypeValueState.create(nullability);
             }
             if (fieldType.isPrimitiveType()) {
-              return ConcretePrimitiveTypeFieldState.create(abstractValue);
+              return ConcretePrimitiveTypeValueState.create(abstractValue);
             }
             assert fieldType.isClassType();
             DynamicType dynamicType =
@@ -219,7 +220,7 @@
                     appView,
                     value.getDynamicType(appView).withNullability(Nullability.maybeNull()),
                     field.getType());
-            return ConcreteClassTypeFieldState.create(abstractValue, dynamicType);
+            return ConcreteClassTypeValueState.create(abstractValue, dynamicType);
           }
 
           if (fieldState.isUnknown()) {
@@ -228,19 +229,19 @@
 
           assert fieldState.isConcrete();
 
-          if (fieldState.isArray()) {
-            ConcreteArrayTypeFieldState arrayFieldState = fieldState.asArray();
-            return arrayFieldState.mutableJoin(appView, field, abstractValue);
+          if (fieldState.isArrayState()) {
+            ConcreteArrayTypeValueState arrayFieldState = fieldState.asArrayState();
+            return arrayFieldState.mutableJoin(appView, field, nullability);
           }
 
-          if (fieldState.isPrimitive()) {
-            ConcretePrimitiveTypeFieldState primitiveFieldState = fieldState.asPrimitive();
+          if (fieldState.isPrimitiveState()) {
+            ConcretePrimitiveTypeValueState primitiveFieldState = fieldState.asPrimitiveState();
             return primitiveFieldState.mutableJoin(appView, field, abstractValue);
           }
 
-          assert fieldState.isClass();
+          assert fieldState.isClassState();
 
-          ConcreteClassTypeFieldState classFieldState = fieldState.asClass();
+          ConcreteClassTypeValueState classFieldState = fieldState.asClassState();
           return classFieldState.mutableJoin(
               appView, abstractValue, value.getDynamicType(appView), field);
         });
@@ -316,15 +317,18 @@
   @SuppressWarnings("ReferenceEquality")
   private void recordAllFieldPutsProcessed(
       ProgramField field, OptimizationFeedbackDelayed feedback) {
-    FieldState fieldState = fieldStates.getOrDefault(field.getDefinition(), FieldState.bottom());
+    ValueState fieldState =
+        fieldStates.getOrDefault(field.getDefinition(), ValueState.bottom(field));
     AbstractValue abstractValue =
-        fieldState.getAbstractValue(appView.abstractValueFactory(), field);
+        fieldState.isBottom()
+            ? appView.abstractValueFactory().createDefaultValue(field.getType())
+            : fieldState.getAbstractValue(appView);
     if (abstractValue.isNonTrivial()) {
       feedback.recordFieldHasAbstractValue(field.getDefinition(), appView, abstractValue);
     }
 
-    if (fieldState.isClass() && field.getOptimizationInfo().getDynamicType().isUnknown()) {
-      ConcreteClassTypeFieldState classFieldState = fieldState.asClass();
+    if (fieldState.isClassState() && field.getOptimizationInfo().getDynamicType().isUnknown()) {
+      ConcreteClassTypeValueState classFieldState = fieldState.asClassState();
       DynamicType dynamicType = classFieldState.getDynamicType();
       if (!dynamicType.isUnknown()) {
         assert WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, field.getType())
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/BottomFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/BottomFieldState.java
deleted file mode 100644
index f71be76..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/BottomFieldState.java
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright (c) 2021, 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.ir.analysis.fieldaccess.state;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Action;
-
-/** Used to represent the state for fields that have never been assigned in the program. */
-public class BottomFieldState extends FieldState {
-
-  private static final BottomFieldState INSTANCE = new BottomFieldState();
-
-  private BottomFieldState() {}
-
-  public static BottomFieldState getInstance() {
-    return INSTANCE;
-  }
-
-  @Override
-  public AbstractValue getAbstractValue(
-      AbstractValueFactory abstractValueFactory, ProgramField field) {
-    return abstractValueFactory.createDefaultValue(field.getType());
-  }
-
-  @Override
-  public boolean isBottom() {
-    return true;
-  }
-
-  @Override
-  public FieldState mutableCopy() {
-    return this;
-  }
-
-  @Override
-  public FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      NonEmptyFieldState fieldState,
-      Action onChangedAction) {
-    return fieldState.mutableCopy();
-  }
-
-  @Override
-  public FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      NonEmptyValueState parameterState,
-      Action onChangedAction) {
-    if (parameterState.isUnknown()) {
-      return unknown();
-    }
-    ConcreteValueState concreteParameterState = parameterState.asConcrete();
-    if (field.getType().isArrayType()) {
-      return ConcreteArrayTypeFieldState.create(
-          concreteParameterState.getAbstractValue(appView), concreteParameterState.copyInFlow());
-    } else if (field.getType().isReferenceType()) {
-      return ConcreteClassTypeFieldState.create(
-          concreteParameterState.getAbstractValue(appView),
-          concreteParameterState.asReferenceParameter().getDynamicType(),
-          concreteParameterState.copyInFlow());
-    } else {
-      assert field.getType().isPrimitiveType();
-      return ConcretePrimitiveTypeFieldState.create(
-          concreteParameterState.getAbstractValue(appView), concreteParameterState.copyInFlow());
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteArrayTypeFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteArrayTypeFieldState.java
deleted file mode 100644
index d8b4587..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteArrayTypeFieldState.java
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (c) 2021, 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.ir.analysis.fieldaccess.state;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteReferenceTypeValueState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlow;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Action;
-import com.android.tools.r8.utils.SetUtils;
-import java.util.Collections;
-import java.util.Set;
-
-/**
- * The information that we track for fields with an array type.
- *
- * <p>Since we don't gain much from tracking the dynamic types of arrays, this is only tracking the
- * abstract value.
- */
-public class ConcreteArrayTypeFieldState extends ConcreteReferenceTypeFieldState {
-
-  public ConcreteArrayTypeFieldState(InFlow inFlow) {
-    this(SetUtils.newHashSet(inFlow));
-  }
-
-  private ConcreteArrayTypeFieldState(Set<InFlow> inFlow) {
-    this(AbstractValue.bottom(), inFlow);
-  }
-
-  @SuppressWarnings("InconsistentOverloads")
-  private ConcreteArrayTypeFieldState(AbstractValue abstractValue, Set<InFlow> inFlow) {
-    super(abstractValue, inFlow);
-  }
-
-  public static NonEmptyFieldState create(AbstractValue abstractValue) {
-    return create(abstractValue, Collections.emptySet());
-  }
-
-  public static NonEmptyFieldState create(AbstractValue abstractValue, Set<InFlow> inFlow) {
-    return abstractValue.isUnknown()
-        ? FieldState.unknown()
-        : new ConcreteArrayTypeFieldState(abstractValue, inFlow);
-  }
-
-  @Override
-  public boolean isArray() {
-    return true;
-  }
-
-  @Override
-  public ConcreteArrayTypeFieldState asArray() {
-    return this;
-  }
-
-  @Override
-  public FieldState mutableCopy() {
-    return new ConcreteArrayTypeFieldState(getAbstractValue(), copyInFlow());
-  }
-
-  public FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView, ProgramField field, AbstractValue abstractValue) {
-    mutableJoinAbstractValue(appView, field, abstractValue);
-    if (isEffectivelyUnknown()) {
-      return unknown();
-    }
-    return this;
-  }
-
-  @Override
-  public FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      ConcreteReferenceTypeFieldState fieldState,
-      Action onChangedAction) {
-    boolean abstractValueChanged = mutableJoinAbstractValue(appView, field, fieldState);
-    if (isEffectivelyUnknown()) {
-      return unknown();
-    }
-    boolean inFlowChanged = mutableJoinInFlow(fieldState);
-    if (widenInFlow(appView)) {
-      return unknown();
-    }
-    if (abstractValueChanged || inFlowChanged) {
-      onChangedAction.execute();
-    }
-    return this;
-  }
-
-  @Override
-  public FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      ConcreteReferenceTypeValueState parameterState,
-      Action onChangedAction) {
-    boolean abstractValueChanged =
-        mutableJoinAbstractValue(appView, field, parameterState.getAbstractValue(appView));
-    if (isEffectivelyUnknown()) {
-      return unknown();
-    }
-    boolean inFlowChanged = mutableJoinInFlow(parameterState.getInFlow());
-    if (widenInFlow(appView)) {
-      return unknown();
-    }
-    if (abstractValueChanged || inFlowChanged) {
-      onChangedAction.execute();
-    }
-    return this;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java
deleted file mode 100644
index 4217f4c..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteClassTypeFieldState.java
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright (c) 2021, 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.ir.analysis.fieldaccess.state;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.ir.analysis.type.DynamicType;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteReferenceTypeValueState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlow;
-import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Action;
-import com.android.tools.r8.utils.SetUtils;
-import java.util.Collections;
-import java.util.Set;
-
-/** The information that we track for fields whose type is a class type. */
-public class ConcreteClassTypeFieldState extends ConcreteReferenceTypeFieldState {
-
-  private DynamicType dynamicType;
-
-  public ConcreteClassTypeFieldState(InFlow inFlow) {
-    this(SetUtils.newHashSet(inFlow));
-  }
-
-  private ConcreteClassTypeFieldState(Set<InFlow> inFlow) {
-    this(AbstractValue.bottom(), DynamicType.bottom(), inFlow);
-  }
-
-  @SuppressWarnings("InconsistentOverloads")
-  private ConcreteClassTypeFieldState(
-      AbstractValue abstractValue, DynamicType dynamicType, Set<InFlow> inFlow) {
-    super(abstractValue, inFlow);
-    this.dynamicType = dynamicType;
-  }
-
-  public static NonEmptyFieldState create(AbstractValue abstractValue, DynamicType dynamicType) {
-    return create(abstractValue, dynamicType, Collections.emptySet());
-  }
-
-  public static NonEmptyFieldState create(
-      AbstractValue abstractValue, DynamicType dynamicType, Set<InFlow> inFlow) {
-    return abstractValue.isUnknown() && dynamicType.isUnknown()
-        ? FieldState.unknown()
-        : new ConcreteClassTypeFieldState(abstractValue, dynamicType, inFlow);
-  }
-
-  @Override
-  public DynamicType getDynamicType() {
-    return dynamicType;
-  }
-
-  @Override
-  public boolean isClass() {
-    return true;
-  }
-
-  @Override
-  public ConcreteClassTypeFieldState asClass() {
-    return this;
-  }
-
-  @Override
-  public boolean isEffectivelyBottom() {
-    return super.isEffectivelyBottom() && dynamicType.isBottom();
-  }
-
-  @Override
-  public boolean isEffectivelyUnknown() {
-    return super.isEffectivelyUnknown() && dynamicType.isUnknown();
-  }
-
-  @Override
-  public FieldState mutableCopy() {
-    return new ConcreteClassTypeFieldState(getAbstractValue(), getDynamicType(), copyInFlow());
-  }
-
-  public FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      AbstractValue abstractValue,
-      DynamicType dynamicType,
-      ProgramField field) {
-    assert field.getType().isClassType();
-    mutableJoinAbstractValue(appView, field, abstractValue);
-    mutableJoinDynamicType(appView, field, dynamicType);
-    if (isEffectivelyUnknown()) {
-      return unknown();
-    }
-    return this;
-  }
-
-  @Override
-  public FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      ConcreteReferenceTypeFieldState fieldState,
-      Action onChangedAction) {
-    boolean abstractValueChanged = mutableJoinAbstractValue(appView, field, fieldState);
-    boolean dynamicTypeChanged = mutableJoinDynamicType(appView, field, fieldState);
-    if (isEffectivelyUnknown()) {
-      return unknown();
-    }
-    boolean inFlowChanged = mutableJoinInFlow(fieldState);
-    if (widenInFlow(appView)) {
-      return unknown();
-    }
-    if (abstractValueChanged || dynamicTypeChanged || inFlowChanged) {
-      onChangedAction.execute();
-    }
-    return this;
-  }
-
-  @Override
-  public FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      ConcreteReferenceTypeValueState parameterState,
-      Action onChangedAction) {
-    boolean abstractValueChanged =
-        mutableJoinAbstractValue(appView, field, parameterState.getAbstractValue(appView));
-    boolean dynamicTypeChanged =
-        mutableJoinDynamicType(appView, field, parameterState.getDynamicType());
-    if (isEffectivelyUnknown()) {
-      return unknown();
-    }
-    boolean inFlowChanged = mutableJoinInFlow(parameterState.getInFlow());
-    if (widenInFlow(appView)) {
-      return unknown();
-    }
-    if (abstractValueChanged || dynamicTypeChanged || inFlowChanged) {
-      onChangedAction.execute();
-    }
-    return this;
-  }
-
-  private boolean mutableJoinDynamicType(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      ConcreteReferenceTypeFieldState fieldState) {
-    return mutableJoinDynamicType(
-        appView,
-        field,
-        fieldState.isClass() ? fieldState.asClass().getDynamicType() : DynamicType.unknown());
-  }
-
-  private boolean mutableJoinDynamicType(
-      AppView<AppInfoWithLiveness> appView, ProgramField field, DynamicType otherDynamicType) {
-    DynamicType oldDynamicType = dynamicType;
-    DynamicType joinedDynamicType = dynamicType.join(appView, otherDynamicType);
-    DynamicType widenedDynamicType =
-        WideningUtils.widenDynamicNonReceiverType(appView, joinedDynamicType, field.getType());
-    dynamicType = widenedDynamicType;
-    return !dynamicType.equals(oldDynamicType);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteFieldState.java
deleted file mode 100644
index 1605e9f..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteFieldState.java
+++ /dev/null
@@ -1,166 +0,0 @@
-// Copyright (c) 2021, 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.ir.analysis.fieldaccess.state;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlow;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Action;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.function.Function;
-
-/** A shared base class for non-trivial field information (neither bottom nor top). */
-public abstract class ConcreteFieldState extends NonEmptyFieldState {
-
-  private AbstractValue abstractValue;
-  private Set<InFlow> inFlow;
-
-  ConcreteFieldState(AbstractValue abstractValue, Set<InFlow> inFlow) {
-    this.abstractValue = abstractValue;
-    this.inFlow = inFlow;
-  }
-
-  @Override
-  public AbstractValue getAbstractValue() {
-    return abstractValue;
-  }
-
-  public boolean hasInFlow() {
-    return !inFlow.isEmpty();
-  }
-
-  public Set<InFlow> getInFlow() {
-    assert inFlow.isEmpty() || inFlow instanceof HashSet<?>;
-    return inFlow;
-  }
-
-  public FieldState clearInFlow() {
-    if (hasInFlow()) {
-      inFlow = Collections.emptySet();
-      if (isEffectivelyBottom()) {
-        return bottom();
-      }
-    }
-    assert !isEffectivelyBottom();
-    return this;
-  }
-
-  public Set<InFlow> copyInFlow() {
-    if (inFlow.isEmpty()) {
-      assert inFlow == Collections.<InFlow>emptySet();
-      return inFlow;
-    }
-    return new HashSet<>(inFlow);
-  }
-
-  @Override
-  public boolean isConcrete() {
-    return true;
-  }
-
-  @Override
-  public ConcreteFieldState asConcrete() {
-    return this;
-  }
-
-  public boolean isEffectivelyBottom() {
-    return abstractValue.isBottom() && !hasInFlow();
-  }
-
-  public boolean isEffectivelyUnknown() {
-    return abstractValue.isUnknown();
-  }
-
-  @Override
-  public final FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      NonEmptyFieldState fieldState,
-      Action onChangedAction) {
-    if (fieldState.isUnknown()) {
-      return fieldState;
-    }
-    ConcreteFieldState concreteFieldState = fieldState.asConcrete();
-    if (isReference()) {
-      assert concreteFieldState.isReference();
-      return asReference()
-          .mutableJoin(appView, field, concreteFieldState.asReference(), onChangedAction);
-    }
-    return asPrimitive()
-        .mutableJoin(appView, field, concreteFieldState.asPrimitive(), onChangedAction);
-  }
-
-  @Override
-  public FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      NonEmptyValueState parameterState,
-      Action onChangedAction) {
-    if (parameterState.isUnknown()) {
-      return unknown();
-    }
-    ConcreteValueState concreteParameterState = parameterState.asConcrete();
-    if (isReference()) {
-      assert concreteParameterState.isReferenceParameter();
-      return asReference()
-          .mutableJoin(
-              appView, field, concreteParameterState.asReferenceParameter(), onChangedAction);
-    }
-    return asPrimitive()
-        .mutableJoin(
-            appView, field, concreteParameterState.asPrimitiveParameter(), onChangedAction);
-  }
-
-  @Override
-  public final FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      Function<FieldState, NonEmptyFieldState> fieldStateSupplier) {
-    return mutableJoin(appView, field, fieldStateSupplier.apply(this));
-  }
-
-  final boolean mutableJoinAbstractValue(
-      AppView<AppInfoWithLiveness> appView, ProgramField field, ConcreteFieldState fieldState) {
-    return mutableJoinAbstractValue(appView, field, fieldState.abstractValue);
-  }
-
-  final boolean mutableJoinAbstractValue(
-      AppView<AppInfoWithLiveness> appView, ProgramField field, AbstractValue otherAbstractValue) {
-    AbstractValue oldAbstractValue = abstractValue;
-    abstractValue =
-        appView.getAbstractValueFieldJoiner().join(abstractValue, otherAbstractValue, field);
-    return !abstractValue.equals(oldAbstractValue);
-  }
-
-  final boolean mutableJoinInFlow(ConcreteFieldState fieldState) {
-    return mutableJoinInFlow(fieldState.inFlow);
-  }
-
-  final boolean mutableJoinInFlow(Set<InFlow> otherInFlow) {
-    if (otherInFlow.isEmpty()) {
-      return false;
-    }
-    if (inFlow.isEmpty()) {
-      assert inFlow == Collections.<InFlow>emptySet();
-      inFlow = new HashSet<>();
-    }
-    return inFlow.addAll(otherInFlow);
-  }
-
-  /**
-   * Returns true if the in-parameters set should be widened to unknown, in which case the entire
-   * parameter state must be widened to unknown.
-   */
-  boolean widenInFlow(AppView<AppInfoWithLiveness> appView) {
-    return inFlow != null
-        && inFlow.size() > appView.options().callSiteOptimizationOptions().getMaxInFlowSize();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcretePrimitiveTypeFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcretePrimitiveTypeFieldState.java
deleted file mode 100644
index 68e767e..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcretePrimitiveTypeFieldState.java
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (c) 2021, 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.ir.analysis.fieldaccess.state;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcretePrimitiveTypeValueState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlow;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Action;
-import com.android.tools.r8.utils.SetUtils;
-import java.util.Collections;
-import java.util.Set;
-
-/** The information that we track for fields whose type is a primitive type. */
-public class ConcretePrimitiveTypeFieldState extends ConcreteFieldState {
-
-  public ConcretePrimitiveTypeFieldState(InFlow inFlow) {
-    this(SetUtils.newHashSet(inFlow));
-  }
-
-  private ConcretePrimitiveTypeFieldState(Set<InFlow> inFlow) {
-    this(AbstractValue.bottom(), inFlow);
-  }
-
-  @SuppressWarnings("InconsistentOverloads")
-  private ConcretePrimitiveTypeFieldState(AbstractValue abstractValue, Set<InFlow> inFlow) {
-    super(abstractValue, inFlow);
-  }
-
-  public static NonEmptyFieldState create(AbstractValue abstractValue) {
-    return create(abstractValue, Collections.emptySet());
-  }
-
-  public static NonEmptyFieldState create(AbstractValue abstractValue, Set<InFlow> inFlow) {
-    return abstractValue.isUnknown()
-        ? FieldState.unknown()
-        : new ConcretePrimitiveTypeFieldState(abstractValue, inFlow);
-  }
-
-  @Override
-  public boolean isPrimitive() {
-    return true;
-  }
-
-  @Override
-  public ConcretePrimitiveTypeFieldState asPrimitive() {
-    return this;
-  }
-
-  @Override
-  public FieldState mutableCopy() {
-    return new ConcretePrimitiveTypeFieldState(getAbstractValue(), copyInFlow());
-  }
-
-  public FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView, ProgramField field, AbstractValue abstractValue) {
-    mutableJoinAbstractValue(appView, field, abstractValue);
-    if (isEffectivelyUnknown()) {
-      return FieldState.unknown();
-    }
-    return this;
-  }
-
-  public FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      ConcretePrimitiveTypeFieldState fieldState,
-      Action onChangedAction) {
-    assert field.getType().isPrimitiveType();
-    boolean abstractValueChanged = mutableJoinAbstractValue(appView, field, fieldState);
-    if (isEffectivelyUnknown()) {
-      return unknown();
-    }
-    boolean inFlowChanged = mutableJoinInFlow(fieldState);
-    if (widenInFlow(appView)) {
-      return unknown();
-    }
-    if (abstractValueChanged || inFlowChanged) {
-      onChangedAction.execute();
-    }
-    return this;
-  }
-
-  public FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      ConcretePrimitiveTypeValueState parameterState,
-      Action onChangedAction) {
-    assert field.getType().isPrimitiveType();
-    boolean abstractValueChanged =
-        mutableJoinAbstractValue(appView, field, parameterState.getAbstractValue());
-    if (isEffectivelyUnknown()) {
-      return unknown();
-    }
-    boolean inFlowChanged = mutableJoinInFlow(parameterState.getInFlow());
-    if (widenInFlow(appView)) {
-      return unknown();
-    }
-    if (abstractValueChanged || inFlowChanged) {
-      onChangedAction.execute();
-    }
-    return this;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteReferenceTypeFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteReferenceTypeFieldState.java
deleted file mode 100644
index f7c99bd..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/ConcreteReferenceTypeFieldState.java
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (c) 2021, 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.ir.analysis.fieldaccess.state;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.ir.analysis.type.DynamicType;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteReferenceTypeValueState;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.InFlow;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Action;
-import java.util.Set;
-
-/** The information that we track for fields whose type is a reference type. */
-public abstract class ConcreteReferenceTypeFieldState extends ConcreteFieldState {
-
-  ConcreteReferenceTypeFieldState(AbstractValue abstractValue, Set<InFlow> inFlow) {
-    super(abstractValue, inFlow);
-  }
-
-  // TODO(b/296030319): Consider leveraging the static field type here. This type may be more
-  //  precise than that of a parameter, for example, unlike the unknown type.
-  public DynamicType getDynamicType() {
-    return DynamicType.unknown();
-  }
-
-  @Override
-  public boolean isReference() {
-    return true;
-  }
-
-  @Override
-  public ConcreteReferenceTypeFieldState asReference() {
-    return this;
-  }
-
-  public abstract FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      ConcreteReferenceTypeFieldState fieldState,
-      Action onChangedAction);
-
-  public abstract FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      ConcreteReferenceTypeValueState parameterState,
-      Action onChangedAction);
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/FieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/FieldState.java
deleted file mode 100644
index 7fee282..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/FieldState.java
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright (c) 2021, 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.ir.analysis.fieldaccess.state;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Action;
-
-/** An abstraction of the runtime values that may flow into each field. */
-public abstract class FieldState {
-
-  public static BottomFieldState bottom() {
-    return BottomFieldState.getInstance();
-  }
-
-  public static UnknownFieldState unknown() {
-    return UnknownFieldState.getInstance();
-  }
-
-  public abstract AbstractValue getAbstractValue(
-      AbstractValueFactory abstractValueFactory, ProgramField field);
-
-  public boolean isArray() {
-    return false;
-  }
-
-  public ConcreteArrayTypeFieldState asArray() {
-    return null;
-  }
-
-  public boolean isBottom() {
-    return false;
-  }
-
-  public boolean isClass() {
-    return false;
-  }
-
-  public ConcreteClassTypeFieldState asClass() {
-    return null;
-  }
-
-  public boolean isConcrete() {
-    return false;
-  }
-
-  public ConcreteFieldState asConcrete() {
-    return null;
-  }
-
-  public NonEmptyFieldState asNonEmpty() {
-    return null;
-  }
-
-  public boolean isPrimitive() {
-    return false;
-  }
-
-  public ConcretePrimitiveTypeFieldState asPrimitive() {
-    return null;
-  }
-
-  public boolean isReference() {
-    return false;
-  }
-
-  public ConcreteReferenceTypeFieldState asReference() {
-    return null;
-  }
-
-  public boolean isUnknown() {
-    return false;
-  }
-
-  public abstract FieldState mutableCopy();
-
-  public final FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView, ProgramField field, NonEmptyFieldState fieldState) {
-    return mutableJoin(appView, field, fieldState, Action.empty());
-  }
-
-  public abstract FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      NonEmptyFieldState fieldState,
-      Action onChangedAction);
-
-  public abstract FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      NonEmptyValueState parameterState,
-      Action onChangedAction);
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/NonEmptyFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/NonEmptyFieldState.java
deleted file mode 100644
index e6bf253..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/NonEmptyFieldState.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) 2024, 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.ir.analysis.fieldaccess.state;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.function.Function;
-
-public abstract class NonEmptyFieldState extends FieldState {
-
-  @Override
-  public NonEmptyFieldState asNonEmpty() {
-    return this;
-  }
-
-  public abstract AbstractValue getAbstractValue();
-
-  @Override
-  public final AbstractValue getAbstractValue(
-      AbstractValueFactory abstractValueFactory, ProgramField field) {
-    return getAbstractValue();
-  }
-
-  public abstract FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      Function<FieldState, NonEmptyFieldState> fieldStateSupplier);
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/UnknownFieldState.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/UnknownFieldState.java
deleted file mode 100644
index b4f2743..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/state/UnknownFieldState.java
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2021, 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.ir.analysis.fieldaccess.state;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.ir.analysis.value.AbstractValue;
-import com.android.tools.r8.optimize.argumentpropagation.codescanner.NonEmptyValueState;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Action;
-import java.util.function.Function;
-
-/** Represents that nothing is known about the values that may flow into a given field. */
-public class UnknownFieldState extends NonEmptyFieldState {
-
-  private static final UnknownFieldState INSTANCE = new UnknownFieldState();
-
-  private UnknownFieldState() {}
-
-  public static UnknownFieldState getInstance() {
-    return INSTANCE;
-  }
-
-  @Override
-  public AbstractValue getAbstractValue() {
-    return AbstractValue.unknown();
-  }
-
-  @Override
-  public boolean isUnknown() {
-    return true;
-  }
-
-  @Override
-  public FieldState mutableCopy() {
-    return this;
-  }
-
-  @Override
-  public FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      NonEmptyFieldState fieldState,
-      Action onChangedAction) {
-    return this;
-  }
-
-  @Override
-  public FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      NonEmptyValueState parameterState,
-      Action onChangedAction) {
-    return this;
-  }
-
-  @Override
-  public FieldState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ProgramField field,
-      Function<FieldState, NonEmptyFieldState> fieldStateSupplier) {
-    return this;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index 0e24e5d..1ea06bf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -160,7 +160,7 @@
       if (staticType.isReferenceType()) {
         DynamicTypeWithUpperBound staticTypeElement = staticType.toDynamicType(appView);
         if (staticType.isArrayType()) {
-          Nullability nullability = concreteParameterState.asArrayParameter().getNullability();
+          Nullability nullability = concreteParameterState.asArrayState().getNullability();
           if (nullability.isDefinitelyNull()) {
             newCallSiteInfo.constants.put(
                 argumentIndex, appView.abstractValueFactory().createNullValue(staticType));
@@ -176,7 +176,7 @@
             assert false;
           }
         } else if (staticType.isClassType()) {
-          DynamicType dynamicType = concreteParameterState.asReferenceParameter().getDynamicType();
+          DynamicType dynamicType = concreteParameterState.asReferenceState().getDynamicType();
           if (!dynamicType.isUnknown()) {
             newCallSiteInfo.dynamicTypes.put(argumentIndex, dynamicType);
             isTop = false;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
index fbe7a42..e4181f2 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorOptimizationInfoPopulator.java
@@ -236,10 +236,10 @@
         argumentIndex < methodState.getParameterStates().size();
         argumentIndex++) {
       ConcreteValueState parameterState = methodState.getParameterState(argumentIndex).asConcrete();
-      if (parameterState == null || !parameterState.isClassParameter()) {
+      if (parameterState == null || !parameterState.isClassState()) {
         continue;
       }
-      DynamicType dynamicType = parameterState.asClassParameter().getDynamicType();
+      DynamicType dynamicType = parameterState.asClassState().getDynamicType();
       DexType staticType = method.getArgumentType(argumentIndex);
       if (shouldWidenDynamicTypeToUnknown(dynamicType, staticType)) {
         methodState.setParameterState(
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeValueState.java
index bf1ba8b..98e6bfc 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomArrayTypeValueState.java
@@ -6,8 +6,6 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteClassTypeFieldState;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteFieldState;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Action;
@@ -25,45 +23,26 @@
   @Override
   public ValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ValueState parameterState,
-      DexType parameterType,
+      ValueState state,
+      DexType staticType,
       StateCloner cloner,
       Action onChangedAction) {
-    if (parameterState.isBottom()) {
+    if (state.isBottom()) {
       return this;
     }
-    if (parameterState.isUnknown()) {
-      return parameterState;
+    if (state.isUnknown()) {
+      return state;
     }
-    assert parameterState.isConcrete();
-    assert parameterState.asConcrete().isReferenceParameter();
-    ConcreteReferenceTypeValueState concreteParameterState =
-        parameterState.asConcrete().asReferenceParameter();
-    if (concreteParameterState.isArrayParameter()) {
-      return cloner.mutableCopy(concreteParameterState);
+    assert state.isConcrete();
+    assert state.asConcrete().isReferenceState();
+    ConcreteReferenceTypeValueState concreteState = state.asConcrete().asReferenceState();
+    if (concreteState.isArrayState()) {
+      return cloner.mutableCopy(concreteState);
     }
-    Nullability nullability = concreteParameterState.getNullability();
+    Nullability nullability = concreteState.getNullability();
     if (nullability.isMaybeNull()) {
       return unknown();
     }
-    return new ConcreteArrayTypeValueState(nullability, concreteParameterState.copyInFlow());
-  }
-
-  @Override
-  public ValueState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ConcreteFieldState fieldState,
-      DexType parameterType,
-      Action onChangedAction) {
-    // We only track nullability for class type fields.
-    if (fieldState.isClass()) {
-      ConcreteClassTypeFieldState classFieldState = fieldState.asClass();
-      Nullability nullability = classFieldState.getDynamicType().getNullability();
-      if (nullability.isUnknown()) {
-        return unknown();
-      }
-      return new ConcreteArrayTypeValueState(nullability, classFieldState.copyInFlow());
-    }
-    return unknown();
+    return new ConcreteArrayTypeValueState(nullability, concreteState.copyInFlow());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeValueState.java
index df28567..f8745d8 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomClassTypeValueState.java
@@ -6,8 +6,6 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteFieldState;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteReferenceTypeFieldState;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.optimize.argumentpropagation.utils.WideningUtils;
@@ -27,43 +25,29 @@
   @Override
   public ValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ValueState parameterState,
-      DexType parameterType,
+      ValueState state,
+      DexType staticType,
       StateCloner cloner,
       Action onChangedAction) {
-    if (parameterState.isBottom()) {
+    if (state.isBottom()) {
       return this;
     }
-    if (parameterState.isUnknown()) {
-      return parameterState;
+    if (state.isUnknown()) {
+      return state;
     }
-    assert parameterState.isConcrete();
-    assert parameterState.asConcrete().isReferenceParameter();
-    ConcreteReferenceTypeValueState concreteParameterState =
-        parameterState.asConcrete().asReferenceParameter();
-    AbstractValue abstractValue = concreteParameterState.getAbstractValue(appView);
-    DynamicType dynamicType = concreteParameterState.getDynamicType();
+    assert state.isConcrete();
+    assert state.asConcrete().isReferenceState();
+    ConcreteReferenceTypeValueState concreteState = state.asConcrete().asReferenceState();
+    AbstractValue abstractValue = concreteState.getAbstractValue(appView);
+    DynamicType dynamicType = concreteState.getDynamicType();
     DynamicType widenedDynamicType =
-        WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, parameterType);
-    if (concreteParameterState.isClassParameter() && !widenedDynamicType.isUnknown()) {
-      return cloner.mutableCopy(concreteParameterState);
+        WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, staticType);
+    if (concreteState.isClassState() && !widenedDynamicType.isUnknown()) {
+      return cloner.mutableCopy(concreteState);
     }
     return abstractValue.isUnknown() && widenedDynamicType.isUnknown()
         ? unknown()
         : new ConcreteClassTypeValueState(
-            abstractValue, widenedDynamicType, concreteParameterState.copyInFlow());
-  }
-
-  @Override
-  public ValueState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ConcreteFieldState fieldState,
-      DexType parameterType,
-      Action onChangedAction) {
-    ConcreteReferenceTypeFieldState referenceFieldState = fieldState.asReference();
-    AbstractValue abstractValue = referenceFieldState.getAbstractValue();
-    DynamicType dynamicType = referenceFieldState.getDynamicType();
-    return new ConcreteClassTypeValueState(
-        abstractValue, dynamicType, referenceFieldState.copyInFlow());
+            abstractValue, widenedDynamicType, concreteState.copyInFlow());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
index e1da2ea..f325f4a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomPrimitiveTypeValueState.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteFieldState;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Action;
 
@@ -23,29 +22,18 @@
   @Override
   public ValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ValueState parameterState,
-      DexType parameterType,
+      ValueState state,
+      DexType staticType,
       StateCloner cloner,
       Action onChangedAction) {
-    if (parameterState.isBottom()) {
-      assert parameterState == bottomPrimitiveTypeParameter();
+    if (state.isBottom()) {
+      assert state == bottomPrimitiveTypeParameter();
       return this;
     }
-    if (parameterState.isUnknown()) {
-      return parameterState;
+    if (state.isUnknown()) {
+      return state;
     }
-    assert parameterState.isConcrete();
-    assert parameterState.asConcrete().isPrimitiveParameter();
-    return cloner.mutableCopy(parameterState);
-  }
-
-  @Override
-  public ValueState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ConcreteFieldState fieldState,
-      DexType parameterType,
-      Action onChangedAction) {
-    return new ConcretePrimitiveTypeValueState(
-        fieldState.getAbstractValue(), fieldState.copyInFlow());
+    assert state.isPrimitiveState();
+    return cloner.mutableCopy(state);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomReceiverValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomReceiverValueState.java
index de17523..b8eb62e 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomReceiverValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/BottomReceiverValueState.java
@@ -6,10 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteClassTypeFieldState;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteFieldState;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
-import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Action;
 
@@ -26,46 +23,26 @@
   @Override
   public ValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ValueState parameterState,
-      DexType parameterType,
+      ValueState state,
+      DexType staticType,
       StateCloner cloner,
       Action onChangedAction) {
-    if (parameterState.isBottom()) {
+    if (state.isBottom()) {
       return this;
     }
-    if (parameterState.isUnknown()) {
-      return parameterState;
+    if (state.isUnknown()) {
+      return state;
     }
-    assert parameterState.isConcrete();
-    assert parameterState.asConcrete().isReferenceParameter();
-    ConcreteReferenceTypeValueState concreteParameterState =
-        parameterState.asConcrete().asReferenceParameter();
-    if (concreteParameterState.isReceiverParameter()) {
-      return cloner.mutableCopy(concreteParameterState);
+    assert state.isConcrete();
+    assert state.asConcrete().isReferenceState();
+    ConcreteReferenceTypeValueState concreteState = state.asConcrete().asReferenceState();
+    if (concreteState.isReceiverState()) {
+      return cloner.mutableCopy(concreteState);
     }
-    DynamicType dynamicType = concreteParameterState.getDynamicType();
+    DynamicType dynamicType = concreteState.getDynamicType();
     if (dynamicType.isUnknown()) {
       return unknown();
     }
-    return new ConcreteReceiverValueState(dynamicType, concreteParameterState.copyInFlow());
-  }
-
-  @Override
-  public ValueState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ConcreteFieldState fieldState,
-      DexType parameterType,
-      Action onChangedAction) {
-    // We only track the dynamic type for class type fields.
-    if (fieldState.isClass()) {
-      ConcreteClassTypeFieldState classFieldState = fieldState.asClass();
-      DynamicType dynamicType = classFieldState.getDynamicType();
-      if (dynamicType.isNotNullType() || dynamicType.isUnknown()) {
-        return unknown();
-      }
-      DynamicType nonNullDynamicType = dynamicType.withNullability(Nullability.definitelyNotNull());
-      return new ConcreteReceiverValueState(nonNullDynamicType, classFieldState.copyInFlow());
-    }
-    return unknown();
+    return new ConcreteReceiverValueState(dynamicType, concreteState.copyInFlow());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
index 6775bf4..46a26d7 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteArrayTypeValueState.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteReferenceTypeFieldState;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -35,6 +35,10 @@
     assert !isEffectivelyUnknown() : "Must use UnknownParameterState instead";
   }
 
+  public static NonEmptyValueState create(Nullability nullability) {
+    return nullability.isUnknown() ? unknown() : new ConcreteArrayTypeValueState(nullability);
+  }
+
   @Override
   public ValueState clearInFlow() {
     if (hasInFlow()) {
@@ -71,12 +75,12 @@
   }
 
   @Override
-  public boolean isArrayParameter() {
+  public boolean isArrayState() {
     return true;
   }
 
   @Override
-  public ConcreteArrayTypeValueState asArrayParameter() {
+  public ConcreteArrayTypeValueState asArrayState() {
     return this;
   }
 
@@ -95,19 +99,27 @@
     return new ConcreteArrayTypeValueState(nullability, copyInFlow());
   }
 
-  @Override
-  public ValueState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ConcreteReferenceTypeValueState parameterState,
-      DexType parameterType,
-      Action onChangedAction) {
-    assert parameterType.isArrayType();
-    assert !nullability.isUnknown();
-    boolean nullabilityChanged = mutableJoinNullability(parameterState.getNullability());
+  public NonEmptyValueState mutableJoin(
+      AppView<AppInfoWithLiveness> appView, ProgramField field, Nullability nullability) {
+    mutableJoinNullability(nullability);
     if (isEffectivelyUnknown()) {
       return unknown();
     }
-    boolean inFlowChanged = mutableJoinInFlow(parameterState);
+    return this;
+  }
+
+  @Override
+  public NonEmptyValueState mutableJoin(
+      AppView<AppInfoWithLiveness> appView,
+      ConcreteReferenceTypeValueState state,
+      DexType staticType,
+      Action onChangedAction) {
+    assert staticType.isArrayType();
+    boolean nullabilityChanged = mutableJoinNullability(state.getNullability());
+    if (isEffectivelyUnknown()) {
+      return unknown();
+    }
+    boolean inFlowChanged = mutableJoinInFlow(state);
     if (widenInFlow(appView)) {
       return unknown();
     }
@@ -117,17 +129,6 @@
     return this;
   }
 
-  @Override
-  public ValueState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ConcreteReferenceTypeFieldState fieldState,
-      DexType parameterType,
-      Action onChangedAction) {
-    assert fieldState.isArray();
-    // We do not track the nullability of array fields.
-    return unknown();
-  }
-
   private boolean mutableJoinNullability(Nullability otherNullability) {
     Nullability oldNullability = nullability;
     nullability = nullability.join(otherNullability);
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
index 4366388..bf3d1f9 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteClassTypeValueState.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteReferenceTypeFieldState;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -39,6 +39,12 @@
     assert !isEffectivelyUnknown() : "Must use UnknownParameterState instead";
   }
 
+  public static NonEmptyValueState create(AbstractValue abstractValue, DynamicType dynamicType) {
+    return abstractValue.isUnknown() && dynamicType.isUnknown()
+        ? ValueState.unknown()
+        : new ConcreteClassTypeValueState(abstractValue, dynamicType);
+  }
+
   @Override
   public ValueState clearInFlow() {
     if (hasInFlow()) {
@@ -77,12 +83,12 @@
   }
 
   @Override
-  public boolean isClassParameter() {
+  public boolean isClassState() {
     return true;
   }
 
   @Override
-  public ConcreteClassTypeValueState asClassParameter() {
+  public ConcreteClassTypeValueState asClassState() {
     return this;
   }
 
@@ -101,45 +107,35 @@
     return new ConcreteClassTypeValueState(abstractValue, dynamicType, copyInFlow());
   }
 
-  @Override
-  public ValueState mutableJoin(
+  public NonEmptyValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ConcreteReferenceTypeValueState parameterState,
-      DexType parameterType,
-      Action onChangedAction) {
-    assert parameterType.isClassType();
-    boolean abstractValueChanged =
-        mutableJoinAbstractValue(appView, parameterState.getAbstractValue(appView), parameterType);
-    boolean dynamicTypeChanged =
-        mutableJoinDynamicType(appView, parameterState.getDynamicType(), parameterType);
+      AbstractValue abstractValue,
+      DynamicType dynamicType,
+      ProgramField field) {
+    assert field.getType().isClassType();
+    mutableJoinAbstractValue(appView, abstractValue, field.getType());
+    mutableJoinDynamicType(appView, dynamicType, field.getType());
     if (isEffectivelyUnknown()) {
       return unknown();
     }
-    boolean inFlowChanged = mutableJoinInFlow(parameterState);
-    if (widenInFlow(appView)) {
-      return unknown();
-    }
-    if (abstractValueChanged || dynamicTypeChanged || inFlowChanged) {
-      onChangedAction.execute();
-    }
     return this;
   }
 
   @Override
-  public ValueState mutableJoin(
+  public NonEmptyValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ConcreteReferenceTypeFieldState fieldState,
-      DexType parameterType,
+      ConcreteReferenceTypeValueState state,
+      DexType staticType,
       Action onChangedAction) {
-    assert parameterType.isClassType();
+    assert staticType.isClassType();
     boolean abstractValueChanged =
-        mutableJoinAbstractValue(appView, fieldState.getAbstractValue(), parameterType);
+        mutableJoinAbstractValue(appView, state.getAbstractValue(appView), staticType);
     boolean dynamicTypeChanged =
-        mutableJoinDynamicType(appView, fieldState.getDynamicType(), parameterType);
+        mutableJoinDynamicType(appView, state.getDynamicType(), staticType);
     if (isEffectivelyUnknown()) {
       return unknown();
     }
-    boolean inFlowChanged = mutableJoinInFlow(fieldState.getInFlow());
+    boolean inFlowChanged = mutableJoinInFlow(state);
     if (widenInFlow(appView)) {
       return unknown();
     }
@@ -150,23 +146,21 @@
   }
 
   private boolean mutableJoinAbstractValue(
-      AppView<AppInfoWithLiveness> appView,
-      AbstractValue otherAbstractValue,
-      DexType parameterType) {
+      AppView<AppInfoWithLiveness> appView, AbstractValue otherAbstractValue, DexType staticType) {
     AbstractValue oldAbstractValue = abstractValue;
     abstractValue =
         appView
             .getAbstractValueParameterJoiner()
-            .join(abstractValue, otherAbstractValue, parameterType);
+            .join(abstractValue, otherAbstractValue, staticType);
     return !abstractValue.equals(oldAbstractValue);
   }
 
   private boolean mutableJoinDynamicType(
-      AppView<AppInfoWithLiveness> appView, DynamicType otherDynamicType, DexType parameterType) {
+      AppView<AppInfoWithLiveness> appView, DynamicType otherDynamicType, DexType staticType) {
     DynamicType oldDynamicType = dynamicType;
     DynamicType joinedDynamicType = dynamicType.join(appView, otherDynamicType);
     DynamicType widenedDynamicType =
-        WideningUtils.widenDynamicNonReceiverType(appView, joinedDynamicType, parameterType);
+        WideningUtils.widenDynamicNonReceiverType(appView, joinedDynamicType, staticType);
     dynamicType = widenedDynamicType;
     return !dynamicType.equals(oldDynamicType);
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java
index 7f0b954..b2b1862 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteMonomorphicMethodState.java
@@ -22,7 +22,7 @@
   public ConcreteMonomorphicMethodState(
       boolean isReturnValueUsed, List<ValueState> parameterStates) {
     assert Streams.stream(Iterables.skip(parameterStates, 1))
-        .noneMatch(x -> x.isConcrete() && x.asConcrete().isReceiverParameter());
+        .noneMatch(x -> x.isConcrete() && x.asConcrete().isReceiverState());
     this.isReturnValueUsed = isReturnValueUsed;
     this.parameterStates = parameterStates;
     assert !isEffectivelyUnknown() : "Must use UnknownMethodState instead";
@@ -102,7 +102,7 @@
           argumentIndex,
           parameterState.mutableJoin(appView, otherParameterState, parameterType, cloner));
       assert !parameterStates.get(argumentIndex).isConcrete()
-          || !parameterStates.get(argumentIndex).asConcrete().isReceiverParameter();
+          || !parameterStates.get(argumentIndex).asConcrete().isReceiverState();
     }
 
     return isEffectivelyUnknown() ? unknown() : this;
@@ -126,7 +126,7 @@
   public void setParameterState(int index, ValueState parameterState) {
     assert index == 0
         || !parameterState.isConcrete()
-        || !parameterState.asConcrete().isReceiverParameter();
+        || !parameterState.asConcrete().isReceiverState();
     parameterStates.set(index, parameterState);
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
index 37686ed..2d53523 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcretePrimitiveTypeValueState.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcretePrimitiveTypeFieldState;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Action;
@@ -29,6 +29,12 @@
     assert !isEffectivelyUnknown() : "Must use UnknownParameterState instead";
   }
 
+  public static NonEmptyValueState create(AbstractValue abstractValue) {
+    return abstractValue.isUnknown()
+        ? ValueState.unknown()
+        : new ConcretePrimitiveTypeValueState(abstractValue);
+  }
+
   public ConcretePrimitiveTypeValueState(InFlow inFlow) {
     this(AbstractValue.bottom(), SetUtils.newHashSet(inFlow));
   }
@@ -50,39 +56,27 @@
     return new ConcretePrimitiveTypeValueState(abstractValue, copyInFlow());
   }
 
-  public ValueState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ConcretePrimitiveTypeValueState parameterState,
-      DexType parameterType,
-      Action onChangedAction) {
-    assert parameterType.isPrimitiveType();
-    boolean abstractValueChanged =
-        mutableJoinAbstractValue(appView, parameterState.getAbstractValue(), parameterType);
+  public NonEmptyValueState mutableJoin(
+      AppView<AppInfoWithLiveness> appView, ProgramField field, AbstractValue abstractValue) {
+    mutableJoinAbstractValue(appView, abstractValue, field.getType());
     if (isEffectivelyUnknown()) {
       return unknown();
     }
-    boolean inFlowChanged = mutableJoinInFlow(parameterState);
-    if (widenInFlow(appView)) {
-      return unknown();
-    }
-    if (abstractValueChanged || inFlowChanged) {
-      onChangedAction.execute();
-    }
     return this;
   }
 
-  public ValueState mutableJoin(
+  public NonEmptyValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ConcretePrimitiveTypeFieldState fieldState,
-      DexType parameterType,
+      ConcretePrimitiveTypeValueState state,
+      DexType staticType,
       Action onChangedAction) {
-    assert parameterType.isPrimitiveType();
+    assert staticType.isPrimitiveType();
     boolean abstractValueChanged =
-        mutableJoinAbstractValue(appView, fieldState.getAbstractValue(), parameterType);
+        mutableJoinAbstractValue(appView, state.getAbstractValue(), staticType);
     if (isEffectivelyUnknown()) {
       return unknown();
     }
-    boolean inFlowChanged = mutableJoinInFlow(fieldState.getInFlow());
+    boolean inFlowChanged = mutableJoinInFlow(state);
     if (widenInFlow(appView)) {
       return unknown();
     }
@@ -93,14 +87,12 @@
   }
 
   private boolean mutableJoinAbstractValue(
-      AppView<AppInfoWithLiveness> appView,
-      AbstractValue otherAbstractValue,
-      DexType parameterType) {
+      AppView<AppInfoWithLiveness> appView, AbstractValue otherAbstractValue, DexType staticType) {
     AbstractValue oldAbstractValue = abstractValue;
     abstractValue =
         appView
             .getAbstractValueParameterJoiner()
-            .join(abstractValue, otherAbstractValue, parameterType);
+            .join(abstractValue, otherAbstractValue, staticType);
     return !abstractValue.equals(oldAbstractValue);
   }
 
@@ -129,12 +121,12 @@
   }
 
   @Override
-  public boolean isPrimitiveParameter() {
+  public boolean isPrimitiveState() {
     return true;
   }
 
   @Override
-  public ConcretePrimitiveTypeValueState asPrimitiveParameter() {
+  public ConcretePrimitiveTypeValueState asPrimitiveState() {
     return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
index d5721d9..186dae0 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReceiverValueState.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteReferenceTypeFieldState;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -74,12 +73,12 @@
   }
 
   @Override
-  public boolean isReceiverParameter() {
+  public boolean isReceiverState() {
     return true;
   }
 
   @Override
-  public ConcreteReceiverValueState asReceiverParameter() {
+  public ConcreteReceiverValueState asReceiverState() {
     return this;
   }
 
@@ -89,41 +88,20 @@
   }
 
   @Override
-  public ValueState mutableJoin(
+  public NonEmptyValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ConcreteReferenceTypeValueState parameterState,
-      DexType parameterType,
+      ConcreteReferenceTypeValueState state,
+      DexType staticType,
       Action onChangedAction) {
     // TODO(b/190154391): Always take in the static type as an argument, and unset the dynamic type
     //  if it equals the static type.
-    assert parameterType == null || parameterType.isClassType();
+    assert staticType == null || staticType.isClassType();
     boolean dynamicTypeChanged =
-        mutableJoinDynamicType(appView, parameterState.getDynamicType(), parameterType);
+        mutableJoinDynamicType(appView, state.getDynamicType(), staticType);
     if (isEffectivelyUnknown()) {
       return unknown();
     }
-    boolean inFlowChanged = mutableJoinInFlow(parameterState);
-    if (widenInFlow(appView)) {
-      return unknown();
-    }
-    if (dynamicTypeChanged || inFlowChanged) {
-      onChangedAction.execute();
-    }
-    return this;
-  }
-
-  @Override
-  public ValueState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ConcreteReferenceTypeFieldState fieldState,
-      DexType parameterType,
-      Action onChangedAction) {
-    boolean dynamicTypeChanged =
-        mutableJoinDynamicType(appView, fieldState.getDynamicType(), parameterType);
-    if (isEffectivelyUnknown()) {
-      return unknown();
-    }
-    boolean inFlowChanged = mutableJoinInFlow(fieldState.getInFlow());
+    boolean inFlowChanged = mutableJoinInFlow(state);
     if (widenInFlow(appView)) {
       return unknown();
     }
@@ -134,12 +112,12 @@
   }
 
   private boolean mutableJoinDynamicType(
-      AppView<AppInfoWithLiveness> appView, DynamicType otherDynamicType, DexType parameterType) {
+      AppView<AppInfoWithLiveness> appView, DynamicType otherDynamicType, DexType staticType) {
     DynamicType oldDynamicType = dynamicType;
     DynamicType joinedDynamicType = dynamicType.join(appView, otherDynamicType);
-    if (parameterType != null) {
+    if (staticType != null) {
       DynamicType widenedDynamicType =
-          WideningUtils.widenDynamicNonReceiverType(appView, joinedDynamicType, parameterType);
+          WideningUtils.widenDynamicNonReceiverType(appView, joinedDynamicType, staticType);
       dynamicType = widenedDynamicType;
     } else {
       dynamicType = joinedDynamicType;
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReferenceTypeValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReferenceTypeValueState.java
index dd23935..4dc4202 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReferenceTypeValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteReferenceTypeValueState.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteReferenceTypeFieldState;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -24,24 +23,18 @@
   public abstract Nullability getNullability();
 
   @Override
-  public boolean isReferenceParameter() {
+  public boolean isReferenceState() {
     return true;
   }
 
   @Override
-  public ConcreteReferenceTypeValueState asReferenceParameter() {
+  public ConcreteReferenceTypeValueState asReferenceState() {
     return this;
   }
 
-  public abstract ValueState mutableJoin(
+  public abstract NonEmptyValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ConcreteReferenceTypeValueState parameterState,
-      DexType parameterType,
-      Action onChangedAction);
-
-  public abstract ValueState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ConcreteReferenceTypeFieldState fieldState,
-      DexType parameterType,
+      ConcreteReferenceTypeValueState state,
+      DexType staticType,
       Action onChangedAction);
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
index 2c7e129..f05f9e6 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ConcreteValueState.java
@@ -6,12 +6,12 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteFieldState;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Action;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.function.Function;
 
 public abstract class ConcreteValueState extends NonEmptyValueState {
 
@@ -53,50 +53,10 @@
 
   public abstract ConcreteParameterStateKind getKind();
 
-  public boolean isArrayParameter() {
-    return false;
-  }
-
-  public ConcreteArrayTypeValueState asArrayParameter() {
-    return null;
-  }
-
-  public boolean isClassParameter() {
-    return false;
-  }
-
-  public ConcreteClassTypeValueState asClassParameter() {
-    return null;
-  }
-
   public abstract boolean isEffectivelyBottom();
 
   public abstract boolean isEffectivelyUnknown();
 
-  public boolean isPrimitiveParameter() {
-    return false;
-  }
-
-  public ConcretePrimitiveTypeValueState asPrimitiveParameter() {
-    return null;
-  }
-
-  public boolean isReceiverParameter() {
-    return false;
-  }
-
-  public ConcreteReceiverValueState asReceiverParameter() {
-    return null;
-  }
-
-  public boolean isReferenceParameter() {
-    return false;
-  }
-
-  public ConcreteReferenceTypeValueState asReferenceParameter() {
-    return null;
-  }
-
   @Override
   public boolean isConcrete() {
     return true;
@@ -108,50 +68,40 @@
   }
 
   @Override
-  public final ValueState mutableJoin(
+  public NonEmptyValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ValueState parameterState,
-      DexType parameterType,
+      Function<ValueState, NonEmptyValueState> stateSupplier,
+      DexType staticType,
       StateCloner cloner,
       Action onChangedAction) {
-    if (parameterState.isBottom()) {
-      return this;
-    }
-    if (parameterState.isUnknown()) {
-      return parameterState;
-    }
-    ConcreteValueState concreteParameterState = parameterState.asConcrete();
-    if (isReferenceParameter()) {
-      assert concreteParameterState.isReferenceParameter();
-      return asReferenceParameter()
-          .mutableJoin(
-              appView,
-              concreteParameterState.asReferenceParameter(),
-              parameterType,
-              onChangedAction);
-    }
-    return asPrimitiveParameter()
-        .mutableJoin(
-            appView, concreteParameterState.asPrimitiveParameter(), parameterType, onChangedAction);
+    return mutableJoin(appView, stateSupplier.apply(this), staticType, cloner, onChangedAction);
   }
 
   @Override
-  public final ValueState mutableJoin(
+  public final NonEmptyValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ConcreteFieldState fieldState,
-      DexType parameterType,
+      ValueState state,
+      DexType staticType,
+      StateCloner cloner,
       Action onChangedAction) {
-    if (isReferenceParameter()) {
-      assert fieldState.isReference();
-      return asReferenceParameter()
-          .mutableJoin(appView, fieldState.asReference(), parameterType, onChangedAction);
+    if (state.isBottom()) {
+      return this;
     }
-    return asPrimitiveParameter()
-        .mutableJoin(appView, fieldState.asPrimitive(), parameterType, onChangedAction);
+    if (state.isUnknown()) {
+      return unknown();
+    }
+    ConcreteValueState concreteState = state.asConcrete();
+    if (isReferenceState()) {
+      assert concreteState.isReferenceState();
+      return asReferenceState()
+          .mutableJoin(appView, concreteState.asReferenceState(), staticType, onChangedAction);
+    }
+    return asPrimitiveState()
+        .mutableJoin(appView, concreteState.asPrimitiveState(), staticType, onChangedAction);
   }
 
-  boolean mutableJoinInFlow(ConcreteValueState parameterState) {
-    return mutableJoinInFlow(parameterState.getInFlow());
+  boolean mutableJoinInFlow(ConcreteValueState state) {
+    return mutableJoinInFlow(state.getInFlow());
   }
 
   boolean mutableJoinInFlow(Set<InFlow> otherInFlow) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/NonEmptyValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/NonEmptyValueState.java
index 4d03f35..19bc95a 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/NonEmptyValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/NonEmptyValueState.java
@@ -4,10 +4,36 @@
 
 package com.android.tools.r8.optimize.argumentpropagation.codescanner;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Action;
+import java.util.function.Function;
+
 public abstract class NonEmptyValueState extends ValueState {
 
   @Override
+  public boolean isNonEmpty() {
+    return true;
+  }
+
+  @Override
   public NonEmptyValueState asNonEmpty() {
     return this;
   }
+
+  public final NonEmptyValueState mutableJoin(
+      AppView<AppInfoWithLiveness> appView,
+      Function<ValueState, NonEmptyValueState> stateSupplier,
+      DexType staticType,
+      StateCloner cloner) {
+    return mutableJoin(appView, stateSupplier, staticType, cloner, Action.empty());
+  }
+
+  public abstract NonEmptyValueState mutableJoin(
+      AppView<AppInfoWithLiveness> appView,
+      Function<ValueState, NonEmptyValueState> stateSupplier,
+      DexType staticType,
+      StateCloner cloner,
+      Action onChangedAction);
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownValueState.java
index eb59e78..f9e7a1d 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/UnknownValueState.java
@@ -6,10 +6,10 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteFieldState;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Action;
+import java.util.function.Function;
 
 public class UnknownValueState extends NonEmptyValueState {
 
@@ -47,10 +47,11 @@
   }
 
   @Override
-  public ValueState mutableJoin(
+  public NonEmptyValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ConcreteFieldState fieldState,
-      DexType parameterType,
+      Function<ValueState, NonEmptyValueState> stateSupplier,
+      DexType staticType,
+      StateCloner cloner,
       Action onChangedAction) {
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
index 1a6e570..4cb4c2b 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/codescanner/ValueState.java
@@ -6,13 +6,25 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.fieldaccess.state.ConcreteFieldState;
+import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.Action;
 
 public abstract class ValueState {
 
+  public static BottomValueState bottom(ProgramField field) {
+    DexType fieldType = field.getType();
+    if (fieldType.isArrayType()) {
+      return bottomArrayTypeParameter();
+    } else if (fieldType.isClassType()) {
+      return bottomClassTypeParameter();
+    } else {
+      assert fieldType.isPrimitiveType();
+      return bottomPrimitiveTypeParameter();
+    }
+  }
+
   public static BottomValueState bottomArrayTypeParameter() {
     return BottomArrayTypeValueState.get();
   }
@@ -35,10 +47,26 @@
 
   public abstract AbstractValue getAbstractValue(AppView<AppInfoWithLiveness> appView);
 
+  public boolean isArrayState() {
+    return false;
+  }
+
+  public ConcreteArrayTypeValueState asArrayState() {
+    return null;
+  }
+
   public boolean isBottom() {
     return false;
   }
 
+  public boolean isClassState() {
+    return false;
+  }
+
+  public ConcreteClassTypeValueState asClassState() {
+    return null;
+  }
+
   public boolean isConcrete() {
     return false;
   }
@@ -47,10 +75,38 @@
     return null;
   }
 
+  public boolean isNonEmpty() {
+    return false;
+  }
+
   public NonEmptyValueState asNonEmpty() {
     return null;
   }
 
+  public boolean isPrimitiveState() {
+    return false;
+  }
+
+  public ConcretePrimitiveTypeValueState asPrimitiveState() {
+    return null;
+  }
+
+  public boolean isReceiverState() {
+    return false;
+  }
+
+  public ConcreteReceiverValueState asReceiverState() {
+    return null;
+  }
+
+  public boolean isReferenceState() {
+    return false;
+  }
+
+  public ConcreteReferenceTypeValueState asReferenceState() {
+    return null;
+  }
+
   public boolean isUnknown() {
     return false;
   }
@@ -59,22 +115,16 @@
 
   public final ValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ValueState parameterState,
+      ValueState state,
       DexType parameterType,
       StateCloner cloner) {
-    return mutableJoin(appView, parameterState, parameterType, cloner, Action.empty());
+    return mutableJoin(appView, state, parameterType, cloner, Action.empty());
   }
 
   public abstract ValueState mutableJoin(
       AppView<AppInfoWithLiveness> appView,
-      ValueState parameterState,
-      DexType parameterType,
+      ValueState state,
+      DexType staticType,
       StateCloner cloner,
       Action onChangedAction);
-
-  public abstract ValueState mutableJoin(
-      AppView<AppInfoWithLiveness> appView,
-      ConcreteFieldState fieldState,
-      DexType parameterType,
-      Action onChangedAction);
 }