Propagate dynamic type information for static fields

Change-Id: Ie387919337c6356a8130d6415f10f2edc9f24d39
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
index ef96f6a..c086a74 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
@@ -5,10 +5,13 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 
 public interface FieldOptimizationFeedback {
 
   void markFieldCannotBeKept(DexEncodedField field);
 
   void markFieldAsPropagated(DexEncodedField field);
+
+  void markFieldHasDynamicType(DexEncodedField field, TypeLatticeElement type);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index d278966..0cbef69 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1203,7 +1203,7 @@
     codeRewriter.rewriteThrowNullPointerException(code);
 
     if (classInitializerDefaultsOptimization != null && !isDebugMode) {
-      classInitializerDefaultsOptimization.optimize(method, code);
+      classInitializerDefaultsOptimization.optimize(method, code, feedback);
     }
     if (Log.ENABLED) {
       Log.debug(getClass(), "Intermediate (SSA) flow graph for %s:\n%s",
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index 1f7a504..e305778 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -29,6 +29,8 @@
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.graph.DexValue.DexValueShort;
 import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -43,6 +45,7 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo;
 import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo.ClassNameMapping;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -101,7 +104,7 @@
     this.dexItemFactory = appView.dexItemFactory();
   }
 
-  public void optimize(DexEncodedMethod method, IRCode code) {
+  public void optimize(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     if (!method.isClassInitializer()) {
       return;
     }
@@ -119,7 +122,8 @@
         findFinalFieldPutsWhileCollectingUnnecessaryStaticPuts(code, clazz, unnecessaryStaticPuts);
 
     // Return eagerly if there is nothing to optimize.
-    if (unnecessaryStaticPuts.isEmpty()) {
+    if (finalFieldPuts.isEmpty()) {
+      assert unnecessaryStaticPuts.isEmpty();
       return;
     }
 
@@ -129,73 +133,85 @@
     for (StaticPut put : finalFieldPuts) {
       DexEncodedField field = appView.appInfo().resolveField(put.getField());
       DexType fieldType = field.field.type;
-      Value inValue = put.value();
-      if (fieldType == dexItemFactory.stringType) {
-        fieldsWithStaticValues.put(field, getDexStringValue(inValue, method.method.holder));
-      } else if (fieldType.isClassType() || fieldType.isArrayType()) {
-        if (inValue.isZero()) {
-          fieldsWithStaticValues.put(field, DexValueNull.NULL);
+      Value value = put.value();
+      if (unnecessaryStaticPuts.contains(put)) {
+        if (fieldType == dexItemFactory.stringType) {
+          fieldsWithStaticValues.put(field, getDexStringValue(value, method.method.holder));
+        } else if (fieldType.isClassType() || fieldType.isArrayType()) {
+          if (value.isZero()) {
+            fieldsWithStaticValues.put(field, DexValueNull.NULL);
+          } else {
+            throw new Unreachable("Unexpected default value for field type " + fieldType + ".");
+          }
         } else {
-          throw new Unreachable("Unexpected default value for field type " + fieldType + ".");
+          ConstNumber cnst = value.getConstInstruction().asConstNumber();
+          if (fieldType == dexItemFactory.booleanType) {
+            fieldsWithStaticValues.put(field, DexValueBoolean.create(cnst.getBooleanValue()));
+          } else if (fieldType == dexItemFactory.byteType) {
+            fieldsWithStaticValues.put(field, DexValueByte.create((byte) cnst.getIntValue()));
+          } else if (fieldType == dexItemFactory.shortType) {
+            fieldsWithStaticValues.put(field, DexValueShort.create((short) cnst.getIntValue()));
+          } else if (fieldType == dexItemFactory.intType) {
+            fieldsWithStaticValues.put(field, DexValueInt.create(cnst.getIntValue()));
+          } else if (fieldType == dexItemFactory.longType) {
+            fieldsWithStaticValues.put(field, DexValueLong.create(cnst.getLongValue()));
+          } else if (fieldType == dexItemFactory.floatType) {
+            fieldsWithStaticValues.put(field, DexValueFloat.create(cnst.getFloatValue()));
+          } else if (fieldType == dexItemFactory.doubleType) {
+            fieldsWithStaticValues.put(field, DexValueDouble.create(cnst.getDoubleValue()));
+          } else if (fieldType == dexItemFactory.charType) {
+            fieldsWithStaticValues.put(field, DexValueChar.create((char) cnst.getIntValue()));
+          } else {
+            throw new Unreachable("Unexpected field type " + fieldType + ".");
+          }
         }
-      } else {
-        ConstNumber cnst = inValue.getConstInstruction().asConstNumber();
-        if (fieldType == dexItemFactory.booleanType) {
-          fieldsWithStaticValues.put(field, DexValueBoolean.create(cnst.getBooleanValue()));
-        } else if (fieldType == dexItemFactory.byteType) {
-          fieldsWithStaticValues.put(field, DexValueByte.create((byte) cnst.getIntValue()));
-        } else if (fieldType == dexItemFactory.shortType) {
-          fieldsWithStaticValues.put(field, DexValueShort.create((short) cnst.getIntValue()));
-        } else if (fieldType == dexItemFactory.intType) {
-          fieldsWithStaticValues.put(field, DexValueInt.create(cnst.getIntValue()));
-        } else if (fieldType == dexItemFactory.longType) {
-          fieldsWithStaticValues.put(field, DexValueLong.create(cnst.getLongValue()));
-        } else if (fieldType == dexItemFactory.floatType) {
-          fieldsWithStaticValues.put(field, DexValueFloat.create(cnst.getFloatValue()));
-        } else if (fieldType == dexItemFactory.doubleType) {
-          fieldsWithStaticValues.put(field, DexValueDouble.create(cnst.getDoubleValue()));
-        } else if (fieldType == dexItemFactory.charType) {
-          fieldsWithStaticValues.put(field, DexValueChar.create((char) cnst.getIntValue()));
-        } else {
-          throw new Unreachable("Unexpected field type " + fieldType + ".");
+      } else if (appView.options().enableFieldTypePropagation && appView.appInfo().hasLiveness()) {
+        AppInfoWithLiveness appInfoWithLiveness = appView.withLiveness().appInfo();
+        if (appInfoWithLiveness.isStaticFieldWrittenOnlyInEnclosingStaticInitializer(field)) {
+          TypeLatticeElement valueType = value.getTypeLattice();
+          assert valueType.strictlyLessThan(
+              TypeLatticeElement.fromDexType(fieldType, Nullability.maybeNull(), appView), appView);
+          feedback.markFieldHasDynamicType(field, valueType);
         }
       }
     }
 
-    // Remove the static put instructions now replaced by static field initial values.
-    Set<Instruction> unnecessaryInstructions = Sets.newIdentityHashSet();
+    if (!unnecessaryStaticPuts.isEmpty()) {
+      // Remove the static put instructions now replaced by static field initial values.
+      Set<Instruction> unnecessaryInstructions = Sets.newIdentityHashSet();
 
-    // Note: Traversing code.instructions(), and not unnecessaryStaticPuts(), to ensure
-    // deterministic iteration order.
-    InstructionListIterator instructionIterator = code.instructionListIterator();
-    while (instructionIterator.hasNext()) {
-      Instruction instruction = instructionIterator.next();
-      if (!instruction.isStaticPut()
-          || !unnecessaryStaticPuts.contains(instruction.asStaticPut())) {
-        continue;
+      // Note: Traversing code.instructions(), and not unnecessaryStaticPuts(), to ensure
+      // deterministic iteration order.
+      InstructionListIterator instructionIterator = code.instructionListIterator();
+      while (instructionIterator.hasNext()) {
+        Instruction instruction = instructionIterator.next();
+        if (!instruction.isStaticPut()
+            || !unnecessaryStaticPuts.contains(instruction.asStaticPut())) {
+          continue;
+        }
+        // Get a hold of the in-value.
+        Value inValue = instruction.asStaticPut().value();
+
+        // Remove the static-put instruction.
+        instructionIterator.removeOrReplaceByDebugLocalRead();
+
+        // Collect, for removal, the instruction that created the value for the static put,
+        // if all users are gone. This is done even if these instructions can throw as for
+        // the current patterns matched these exceptions are not detectable.
+        if (inValue.numberOfAllUsers() > 0) {
+          continue;
+        }
+        if (inValue.isConstString()) {
+          unnecessaryInstructions.add(inValue.definition);
+        } else if (!inValue.isPhi() && inValue.definition.isInvokeVirtual()) {
+          unnecessaryInstructions.add(inValue.definition);
+        }
       }
-      // Get a hold of the in-value.
-      Value inValue = instruction.asStaticPut().value();
 
-      // Remove the static-put instruction.
-      instructionIterator.removeOrReplaceByDebugLocalRead();
-
-      // Collect, for removal, the instruction that created the value for the static put,
-      // if all users are gone. This is done even if these instructions can throw as for
-      // the current patterns matched these exceptions are not detectable.
-      if (inValue.numberOfAllUsers() > 0) {
-        continue;
+      // Remove the instructions collected for removal.
+      if (unnecessaryInstructions.size() > 0) {
+        IteratorUtils.removeIf(code.instructionListIterator(), unnecessaryInstructions::contains);
       }
-      if (inValue.isConstString()) {
-        unnecessaryInstructions.add(inValue.definition);
-      } else if (!inValue.isPhi() && inValue.definition.isInvokeVirtual()) {
-        unnecessaryInstructions.add(inValue.definition);
-      }
-    }
-
-    // Remove the instructions collected for removal.
-    if (unnecessaryInstructions.size() > 0) {
-      IteratorUtils.removeIf(code.instructionListIterator(), unnecessaryInstructions::contains);
     }
 
     // If we are in R8, and we have removed all static-put instructions to some field, then record
@@ -208,6 +224,7 @@
         // First collect all the candidate fields that are *potentially* no longer being written to.
         Set<DexField> candidates =
             finalFieldPuts.stream()
+                .filter(unnecessaryStaticPuts::contains)
                 .map(FieldInstruction::getField)
                 .map(appInfoWithLiveness::resolveField)
                 .filter(appInfoWithLiveness::isStaticFieldWrittenOnlyInEnclosingStaticInitializer)
@@ -350,17 +367,19 @@
               return validateFinalFieldPuts(finalFieldPuts, isWrittenBefore);
             }
             DexField field = put.getField();
+            Value value = put.value();
+            TypeLatticeElement valueType = value.getTypeLattice();
             if (clazz.definesStaticField(field)) {
               if (isReadBefore.contains(field)) {
                 // Promoting this put to a class constant would cause a previous static-get
                 // instruction to read a different value.
                 continue;
               }
-              if (put.value().isDexItemBasedConstStringThatNeedsToComputeClassName()) {
+              if (value.isDexItemBasedConstStringThatNeedsToComputeClassName()) {
                 continue;
               }
-              if (put.value().isConstant()) {
-                if (field.type.isReferenceType() && put.value().isZero()) {
+              if (value.isConstant()) {
+                if (field.type.isReferenceType() && value.isZero()) {
                   finalFieldPuts.put(field, put);
                   unnecessaryStaticPuts.add(put);
                   // If this field has been written before, those static-put's up to this point are
@@ -393,6 +412,9 @@
                   isWrittenBefore.remove(field);
                 }
                 continue;
+              } else if (valueType.isReference() && valueType.isDefinitelyNotNull()) {
+                finalFieldPuts.put(field, put);
+                continue;
               }
               // static-put that is reaching here can be redundant if the corresponding field is
               // rewritten with another constant (of course before being read).
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
index 1a1aacb..1b36254 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -17,6 +18,7 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.JumpInstruction;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -55,12 +57,14 @@
     InstructionListIterator instructionIterator = block.listIterator(code);
     while (instructionIterator.hasNext()) {
       Instruction current = instructionIterator.next();
+      Value outValue = current.outValue();
+      if (outValue == null) {
+        continue;
+      }
+
+      TypeLatticeElement dynamicType;
       if (current.isInvokeMethod()) {
         InvokeMethod invoke = current.asInvokeMethod();
-        Value outValue = invoke.outValue();
-        if (outValue == null) {
-          continue;
-        }
 
         DexType staticReturnTypeRaw = invoke.getInvokedMethod().proto.returnType;
         if (!staticReturnTypeRaw.isReferenceType()) {
@@ -78,33 +82,44 @@
           continue;
         }
 
-        TypeLatticeElement dynamicReturnType = optimizationInfo.getDynamicReturnType();
-        if (dynamicReturnType == null
-            || !dynamicReturnType.strictlyLessThan(outValue.getTypeLattice(), appView)) {
+        dynamicType = optimizationInfo.getDynamicReturnType();
+      } else if (current.isStaticGet()) {
+        StaticGet staticGet = current.asStaticGet();
+        DexEncodedField encodedField = appView.appInfo().resolveField(staticGet.getField());
+        if (encodedField == null) {
           continue;
         }
 
-        // Split block if needed (only debug instructions are allowed after the throwing
-        // instruction, if any).
-        BasicBlock insertionBlock =
-            block.hasCatchHandlers() ? instructionIterator.split(code, blockIterator) : block;
+        dynamicType = encodedField.getOptimizationInfo().getDynamicType();
+      } else {
+        continue;
+      }
 
-        // Replace usages of out-value by the out-value of the AssumeDynamicType instruction.
-        Value specializedOutValue =
-            code.createValue(outValue.getTypeLattice(), outValue.getLocalInfo());
-        outValue.replaceUsers(specializedOutValue);
+      if (dynamicType == null
+          || !dynamicType.strictlyLessThan(outValue.getTypeLattice(), appView)) {
+        continue;
+      }
 
-        // Insert AssumeDynamicType instruction.
-        Assume<DynamicTypeAssumption> assumeInstruction =
-            Assume.createAssumeDynamicTypeInstruction(
-                dynamicReturnType, specializedOutValue, outValue, invoke, appView);
-        assumeInstruction.setPosition(
-            appView.options().debug ? invoke.getPosition() : Position.none());
-        if (insertionBlock == block) {
-          instructionIterator.add(assumeInstruction);
-        } else {
-          insertionBlock.listIterator(code).add(assumeInstruction);
-        }
+      // Split block if needed (only debug instructions are allowed after the throwing
+      // instruction, if any).
+      BasicBlock insertionBlock =
+          block.hasCatchHandlers() ? instructionIterator.split(code, blockIterator) : block;
+
+      // Replace usages of out-value by the out-value of the AssumeDynamicType instruction.
+      Value specializedOutValue =
+          code.createValue(outValue.getTypeLattice(), outValue.getLocalInfo());
+      outValue.replaceUsers(specializedOutValue);
+
+      // Insert AssumeDynamicType instruction.
+      Assume<DynamicTypeAssumption> assumeInstruction =
+          Assume.createAssumeDynamicTypeInstruction(
+              dynamicType, specializedOutValue, outValue, current, appView);
+      assumeInstruction.setPosition(
+          appView.options().debug ? current.getPosition() : Position.none());
+      if (insertionBlock == block) {
+        instructionIterator.add(assumeInstruction);
+      } else {
+        insertionBlock.listIterator(code).add(assumeInstruction);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index e9dae85..17fea4b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -368,46 +368,56 @@
     }
 
     // Check if a this value is known const.
-    if (!appView.appInfo().isPinned(target.field)) {
-      ConstInstruction replacement = target.valueAsConstInstruction(code, current.dest(), appView);
-      if (replacement != null) {
-        affectedValues.addAll(current.outValue().affectedValues());
-        iterator.replaceCurrentInstruction(replacement);
-        if (replacement.isDexItemBasedConstString()) {
-          code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
-        }
-        feedback.markFieldAsPropagated(target);
-        return;
-      }
+    if (appView.appInfo().isPinned(target.field)) {
+      return;
     }
 
-    if (current.dest() != null) {
-      // In case the class holder of this static field satisfying following criteria:
-      //   -- cannot trigger other static initializer except for its own
-      //   -- is final
-      //   -- has a class initializer which is classified as trivial
-      //      (see CodeRewriter::computeClassInitializerInfo) and
-      //      initializes the field being accessed
-      //
-      // ... and the field itself is not pinned by keep rules (in which case it might
-      // be updated outside the class constructor, e.g. via reflections), it is safe
-      // to assume that the static-get instruction reads the value it initialized value
-      // in class initializer and is never null.
-      DexClass holderDefinition = appView.definitionFor(field.holder);
-      if (holderDefinition != null
-          && holderDefinition.accessFlags.isFinal()
-          && !field.holder.initializationOfParentTypesMayHaveSideEffects(appView)) {
-        Value outValue = current.dest();
-        DexEncodedMethod classInitializer = holderDefinition.getClassInitializer();
-        if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) {
-          TrivialInitializer info =
-              classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
-          if (info != null
-              && ((TrivialClassInitializer) info).field == field
-              && !appView.appInfo().isPinned(field)
-              && outValue.getTypeLattice().isReference()
-              && outValue.canBeNull()) {
-            insertAssumeNotNull(code, affectedValues, blocks, iterator, current);
+    ConstInstruction replacement = target.valueAsConstInstruction(code, current.dest(), appView);
+    if (replacement != null) {
+      affectedValues.addAll(current.outValue().affectedValues());
+      iterator.replaceCurrentInstruction(replacement);
+      if (replacement.isDexItemBasedConstString()) {
+        code.method.getMutableOptimizationInfo().markUseIdentifierNameString();
+      }
+      feedback.markFieldAsPropagated(target);
+      return;
+    }
+
+    if (current.hasOutValue()) {
+      Value outValue = current.outValue();
+      TypeLatticeElement outType = outValue.getTypeLattice();
+      if (outType.isReference() && outType.isNullable()) {
+        TypeLatticeElement dynamicType = target.getOptimizationInfo().getDynamicType();
+        if (dynamicType != null && dynamicType.isDefinitelyNotNull()) {
+          insertAssumeNotNull(code, affectedValues, blocks, iterator, current);
+          return;
+        }
+
+        // In case the class holder of this static field satisfying following criteria:
+        //   -- cannot trigger other static initializer except for its own
+        //   -- is final
+        //   -- has a class initializer which is classified as trivial
+        //      (see CodeRewriter::computeClassInitializerInfo) and
+        //      initializes the field being accessed
+        //
+        // ... and the field itself is not pinned by keep rules (in which case it might
+        // be updated outside the class constructor, e.g. via reflections), it is safe
+        // to assume that the static-get instruction reads the value it initialized value
+        // in class initializer and is never null.
+        DexClass holderDefinition = appView.definitionFor(field.holder);
+        if (holderDefinition != null
+            && holderDefinition.accessFlags.isFinal()
+            && !field.holder.initializationOfParentTypesMayHaveSideEffects(appView)) {
+          DexEncodedMethod classInitializer = holderDefinition.getClassInitializer();
+          if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) {
+            TrivialInitializer info =
+                classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
+            if (info != null
+                && ((TrivialClassInitializer) info).field == field
+                && outValue.getTypeLattice().isReference()
+                && outValue.canBeNull()) {
+              insertAssumeNotNull(code, affectedValues, blocks, iterator, current);
+            }
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
index 9b1b0df..50c16fd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+
 public class DefaultFieldOptimizationInfo extends FieldOptimizationInfo {
 
   private static final DefaultFieldOptimizationInfo INSTANCE = new DefaultFieldOptimizationInfo();
@@ -25,6 +27,11 @@
   }
 
   @Override
+  public TypeLatticeElement getDynamicType() {
+    return null;
+  }
+
+  @Override
   public boolean valueHasBeenPropagated() {
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
index 3f5983e..4e9ef49 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
@@ -4,12 +4,16 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+
 public abstract class FieldOptimizationInfo {
 
   public abstract MutableFieldOptimizationInfo mutableCopy();
 
   public abstract boolean cannotBeKept();
 
+  public abstract TypeLatticeElement getDynamicType();
+
   public abstract boolean valueHasBeenPropagated();
 
   public boolean isDefaultFieldOptimizationInfo() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index 2b6ae1b..ed26f85 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+
 /**
  * Optimization info for fields.
  *
@@ -15,6 +17,7 @@
 
   private boolean cannotBeKept = false;
   private boolean valueHasBeenPropagated = false;
+  private TypeLatticeElement dynamicType = null;
 
   @Override
   public MutableFieldOptimizationInfo mutableCopy() {
@@ -34,6 +37,15 @@
   }
 
   @Override
+  public TypeLatticeElement getDynamicType() {
+    return dynamicType;
+  }
+
+  public void setDynamicType(TypeLatticeElement type) {
+    dynamicType = type;
+  }
+
+  @Override
   public boolean valueHasBeenPropagated() {
     return valueHasBeenPropagated;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index e454b27..d5a2646 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -83,6 +83,11 @@
     getFieldOptimizationInfoForUpdating(field).markAsPropagated();
   }
 
+  @Override
+  public void markFieldHasDynamicType(DexEncodedField field, TypeLatticeElement type) {
+    getFieldOptimizationInfoForUpdating(field).setDynamicType(type);
+  }
+
   // METHOD OPTIMIZATION INFO:
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 1d45828..4840e15 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -34,6 +34,9 @@
   @Override
   public void markFieldAsPropagated(DexEncodedField field) {}
 
+  @Override
+  public void markFieldHasDynamicType(DexEncodedField field, TypeLatticeElement type) {}
+
   // METHOD OPTIMIZATION INFO:
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 4cee63d..af7a717 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -38,6 +38,11 @@
     field.getMutableOptimizationInfo().markAsPropagated();
   }
 
+  @Override
+  public void markFieldHasDynamicType(DexEncodedField field, TypeLatticeElement type) {
+    // Ignored.
+  }
+
   // METHOD OPTIMIZATION INFO.
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index d3e5566..6c01902 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -237,6 +237,7 @@
   public boolean enableInitializedClassesInInstanceMethodsAnalysis = true;
   public boolean enableRedundantFieldLoadElimination = true;
   public boolean enableValuePropagation = true;
+  public boolean enableFieldTypePropagation = true;
   public boolean enableUninstantiatedTypeOptimization = true;
   // TODO(b/138917494): Disable until we have numbers on potential performance penalties.
   public boolean enableRedundantConstNumberOptimization = false;
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index 76b0115..9f20f9e 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -16,13 +16,9 @@
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OffOrAuto;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
-import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -31,11 +27,9 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 import java.util.function.UnaryOperator;
 import java.util.stream.Collectors;
 import org.junit.Rule;
@@ -45,7 +39,8 @@
 
 public abstract class RunExamplesJava9Test
     <B extends BaseCommand.Builder<? extends BaseCommand, B>> {
-  static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_JAVA9_BUILD_DIR;
+
+  private static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_JAVA9_BUILD_DIR;
 
   abstract class TestRunner<C extends TestRunner<C>> {
     final String testName;
@@ -72,45 +67,11 @@
       return self();
     }
 
-    C withClassCheck(Consumer<FoundClassSubject> check) {
-      return withDexCheck(inspector -> inspector.forAllClasses(check));
-    }
-
-    C withMethodCheck(Consumer<FoundMethodSubject> check) {
-      return withClassCheck(clazz -> clazz.forAllMethods(check));
-    }
-
     C withArg(String arg) {
       args.add(arg);
       return self();
     }
 
-    <T extends InstructionSubject> C withInstructionCheck(
-        Predicate<InstructionSubject> filter, Consumer<T> check) {
-      return withMethodCheck(method -> {
-        if (method.isAbstract()) {
-          return;
-        }
-        Iterator<T> iterator = method.iterateInstructions(filter);
-        while (iterator.hasNext()) {
-          check.accept(iterator.next());
-        }
-      });
-    }
-
-    C withOptionConsumer(Consumer<InternalOptions> consumer) {
-      optionConsumers.add(consumer);
-      return self();
-    }
-
-    C withInterfaceMethodDesugaring(OffOrAuto behavior) {
-      return withOptionConsumer(o -> o.interfaceMethodDesugaring = behavior);
-    }
-
-    C withTryWithResourcesDesugaring(OffOrAuto behavior) {
-      return withOptionConsumer(o -> o.tryWithResourcesDesugaring = behavior);
-    }
-
     void combinedOptionConsumer(InternalOptions options) {
       for (Consumer<InternalOptions> consumer : optionConsumers) {
         consumer.accept(options);
@@ -122,10 +83,6 @@
       return self();
     }
 
-    C withMainDexClass(String... classes) {
-      return withBuilderTransformation(builder -> builder.addMainDexClasses(classes));
-    }
-
     Path build() throws Throwable {
       Path inputFile = getInputJar();
       Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
@@ -250,17 +207,17 @@
   @Rule
   public TestDescriptionWatcher watcher = new TestDescriptionWatcher();
 
-  boolean failsOn(Map<DexVm.Version, List<String>> failsOn, String name) {
+  private boolean failsOn(Map<DexVm.Version, List<String>> failsOn, String name) {
     DexVm.Version vmVersion = ToolHelper.getDexVm().getVersion();
     return failsOn.containsKey(vmVersion)
         && failsOn.get(vmVersion).contains(name);
   }
 
-  boolean expectedToFail(String name) {
+  private boolean expectedToFail(String name) {
     return failsOn(failsOn, name);
   }
 
-  boolean minSdkErrorExpected(String testName) {
+  private boolean minSdkErrorExpected(String testName) {
     return minSdkErrorExpected.contains(testName);
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullFieldTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullStaticFieldTest.java
similarity index 81%
rename from src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullFieldTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullStaticFieldTest.java
index 81ba319..e131f13 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullFieldTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullStaticFieldTest.java
@@ -6,8 +6,9 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -21,7 +22,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class NonNullFieldTest extends TestBase {
+public class NonNullStaticFieldTest extends TestBase {
 
   private final TestParameters parameters;
 
@@ -30,7 +31,7 @@
     return getTestParameters().withAllRuntimes().build();
   }
 
-  public NonNullFieldTest(TestParameters parameters) {
+  public NonNullStaticFieldTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
@@ -39,6 +40,7 @@
     testForR8(parameters.getBackend())
         .addProgramClasses(TestClass.class)
         .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
         .setMinApi(parameters.getRuntime())
         .compile()
         .inspect(this::verifyMainIsEmpty)
@@ -52,8 +54,7 @@
 
     MethodSubject mainMethodSubject = testClassSubject.mainMethod();
     assertThat(mainMethodSubject, isPresent());
-    // TODO(b/140161397): Should propagate the fact that `field` is guaranteed to be non-null.
-    assertFalse(mainMethodSubject.streamInstructions().allMatch(InstructionSubject::isReturnVoid));
+    assertTrue(mainMethodSubject.streamInstructions().allMatch(InstructionSubject::isReturnVoid));
   }
 
   static class TestClass {
@@ -62,8 +63,13 @@
 
     public static void main(String[] args) {
       if (field == null) {
-        throw new NullPointerException();
+        dead();
       }
     }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Unreachable!");
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullFieldTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StaticFieldWithRefinedTypeTest.java
similarity index 72%
copy from src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullFieldTest.java
copy to src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StaticFieldWithRefinedTypeTest.java
index 81ba319..7cf1f42 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/NonNullFieldTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/StaticFieldWithRefinedTypeTest.java
@@ -6,8 +6,10 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -21,7 +23,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class NonNullFieldTest extends TestBase {
+public class StaticFieldWithRefinedTypeTest extends TestBase {
 
   private final TestParameters parameters;
 
@@ -30,15 +32,17 @@
     return getTestParameters().withAllRuntimes().build();
   }
 
-  public NonNullFieldTest(TestParameters parameters) {
+  public StaticFieldWithRefinedTypeTest(TestParameters parameters) {
     this.parameters = parameters;
   }
 
   @Test
   public void test() throws Exception {
     testForR8(parameters.getBackend())
-        .addProgramClasses(TestClass.class)
+        .addInnerClasses(StaticFieldWithRefinedTypeTest.class)
         .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
         .setMinApi(parameters.getRuntime())
         .compile()
         .inspect(this::verifyMainIsEmpty)
@@ -52,18 +56,27 @@
 
     MethodSubject mainMethodSubject = testClassSubject.mainMethod();
     assertThat(mainMethodSubject, isPresent());
-    // TODO(b/140161397): Should propagate the fact that `field` is guaranteed to be non-null.
-    assertFalse(mainMethodSubject.streamInstructions().allMatch(InstructionSubject::isReturnVoid));
+    assertTrue(mainMethodSubject.streamInstructions().allMatch(InstructionSubject::isReturnVoid));
   }
 
   static class TestClass {
 
-    static Object field = new Object();
+    static A field = new B();
 
     public static void main(String[] args) {
-      if (field == null) {
-        throw new NullPointerException();
+      if (!(field instanceof B)) {
+        dead();
       }
     }
+
+    @NeverInline
+    static void dead() {
+      System.out.println("Unreachable!");
+    }
   }
+
+  @NeverMerge
+  static class A {}
+
+  static class B extends A {}
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
index b76a4fb..b32bc04 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -7,7 +7,6 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
@@ -15,6 +14,7 @@
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.google.common.base.Predicates;
 import java.util.concurrent.atomic.AtomicInteger;
 import org.junit.Test;
 
@@ -40,16 +40,14 @@
           assertThat(inspector.clazz("class_staticizer.Regular$Companion"), isPresent());
           assertThat(inspector.clazz("class_staticizer.Derived$Companion"), isPresent());
 
+          // The Util class is there, but its instance methods have been inlined.
           ClassSubject utilClass = inspector.clazz("class_staticizer.Util");
           assertThat(utilClass, isPresent());
           AtomicInteger nonStaticMethodCount = new AtomicInteger();
-          utilClass.forAllMethods(
-              method -> {
-                if (!method.isStatic()) {
-                  nonStaticMethodCount.incrementAndGet();
-                }
-              });
-          assertEquals(4, nonStaticMethodCount.get());
+          assertTrue(
+              utilClass.allMethods().stream()
+                  .filter(Predicates.not(FoundMethodSubject::isStatic))
+                  .allMatch(FoundMethodSubject::isInstanceInitializer));
         });
 
     // With class staticizer.
@@ -77,7 +75,6 @@
         options -> {
           options.enableClassInlining = false;
           options.enableClassStaticizer = enabled;
-          options.enableInliningOfInvokesWithNullableReceivers = false;
         },
         inspector);
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index 7f038cc..fcb2872 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -89,9 +89,6 @@
         o.enableClassStaticizer = false;
       };
 
-  private Consumer<InternalOptions> disableInliningOfInvokesWithNullableReceivers =
-      o -> o.enableInliningOfInvokesWithNullableReceivers = false;
-
   public R8KotlinPropertiesTest(
       KotlinTargetVersion targetVersion, boolean allowAccessModification) {
     super(targetVersion, allowAccessModification);
@@ -416,7 +413,7 @@
     runTest(
         PACKAGE_NAME,
         mainClass,
-        disableAggressiveClassOptimizations.andThen(disableInliningOfInvokesWithNullableReceivers),
+        disableAggressiveClassOptimizations,
         app -> {
           CodeInspector codeInspector = new CodeInspector(app);
           ClassSubject outerClass =
@@ -426,21 +423,19 @@
           String propertyName = "primitiveProp";
           FieldSubject fieldSubject = checkFieldIsKept(outerClass, "int", propertyName);
           assertTrue(fieldSubject.getField().accessFlags.isStatic());
-
-          MemberNaming.MethodSignature getter =
-              COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
-          MemberNaming.MethodSignature setter =
-              COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
-
-          // Getter and setter cannot be inlined because we don't know if null check semantic is
-          // preserved.
-          checkMethodIsKept(companionClass, getter);
-          checkMethodIsKept(companionClass, setter);
           if (allowAccessModification) {
             assertTrue(fieldSubject.getField().accessFlags.isPublic());
           } else {
             assertTrue(fieldSubject.getField().accessFlags.isPrivate());
           }
+
+          MemberNaming.MethodSignature getter =
+              COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
+          checkMethodIsRemoved(companionClass, getter);
+
+          MemberNaming.MethodSignature setter =
+              COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
+          checkMethodIsRemoved(companionClass, setter);
         });
   }
 
@@ -489,7 +484,7 @@
     runTest(
         PACKAGE_NAME,
         mainClass,
-        disableAggressiveClassOptimizations.andThen(disableInliningOfInvokesWithNullableReceivers),
+        disableAggressiveClassOptimizations,
         app -> {
           CodeInspector codeInspector = new CodeInspector(app);
           ClassSubject outerClass =
@@ -499,21 +494,18 @@
           String propertyName = "internalProp";
           FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
           assertTrue(fieldSubject.getField().accessFlags.isStatic());
-
-          MemberNaming.MethodSignature getter =
-              COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
-          MemberNaming.MethodSignature setter =
-              COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
-
-          // Getter and setter cannot be inlined because we don't know if null check semantic is
-          // preserved.
-          checkMethodIsKept(companionClass, getter);
-          checkMethodIsKept(companionClass, setter);
           if (allowAccessModification) {
             assertTrue(fieldSubject.getField().accessFlags.isPublic());
           } else {
             assertTrue(fieldSubject.getField().accessFlags.isPrivate());
           }
+
+          MemberNaming.MethodSignature getter =
+              COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
+          checkMethodIsRemoved(companionClass, getter);
+          MemberNaming.MethodSignature setter =
+              COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
+          checkMethodIsRemoved(companionClass, setter);
         });
   }
 
@@ -524,7 +516,7 @@
     runTest(
         PACKAGE_NAME,
         mainClass,
-        disableAggressiveClassOptimizations.andThen(disableInliningOfInvokesWithNullableReceivers),
+        disableAggressiveClassOptimizations,
         app -> {
           CodeInspector codeInspector = new CodeInspector(app);
           ClassSubject outerClass =
@@ -534,21 +526,19 @@
           String propertyName = "publicProp";
           FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
           assertTrue(fieldSubject.getField().accessFlags.isStatic());
-
-          MemberNaming.MethodSignature getter =
-              COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
-          MemberNaming.MethodSignature setter =
-              COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
-
-          // Getter and setter cannot be inlined because we don't know if null check semantic is
-          // preserved.
-          checkMethodIsKept(companionClass, getter);
-          checkMethodIsKept(companionClass, setter);
           if (allowAccessModification) {
             assertTrue(fieldSubject.getField().accessFlags.isPublic());
           } else {
             assertTrue(fieldSubject.getField().accessFlags.isPrivate());
           }
+
+          MemberNaming.MethodSignature getter =
+              COMPANION_PROPERTY_CLASS.getGetterForProperty(propertyName);
+          checkMethodIsRemoved(companionClass, getter);
+
+          MemberNaming.MethodSignature setter =
+              COMPANION_PROPERTY_CLASS.getSetterForProperty(propertyName);
+          checkMethodIsRemoved(companionClass, setter);
         });
   }
 
@@ -596,7 +586,7 @@
     runTest(
         PACKAGE_NAME,
         mainClass,
-        disableAggressiveClassOptimizations.andThen(disableInliningOfInvokesWithNullableReceivers),
+        disableAggressiveClassOptimizations,
         app -> {
           CodeInspector codeInspector = new CodeInspector(app);
           ClassSubject outerClass =
@@ -605,15 +595,13 @@
           String propertyName = "internalLateInitProp";
           FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
           assertTrue(fieldSubject.getField().accessFlags.isStatic());
+          assertTrue(fieldSubject.getField().accessFlags.isPublic());
 
           MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
-          MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+          checkMethodIsRemoved(companionClass, getter);
 
-          // Getter and setter cannot be inlined because we don't know if null check semantic is
-          // preserved.
-          checkMethodIsKept(companionClass, getter);
-          checkMethodIsKept(companionClass, setter);
-          assertTrue(fieldSubject.getField().accessFlags.isPublic());
+          MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+          checkMethodIsRemoved(companionClass, setter);
         });
   }
 
@@ -625,7 +613,7 @@
     runTest(
         PACKAGE_NAME,
         mainClass,
-        disableAggressiveClassOptimizations.andThen(disableInliningOfInvokesWithNullableReceivers),
+        disableAggressiveClassOptimizations,
         app -> {
           CodeInspector codeInspector = new CodeInspector(app);
           ClassSubject outerClass =
@@ -634,15 +622,13 @@
           String propertyName = "publicLateInitProp";
           FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
           assertTrue(fieldSubject.getField().accessFlags.isStatic());
+          assertTrue(fieldSubject.getField().accessFlags.isPublic());
 
           MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
-          MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+          checkMethodIsRemoved(companionClass, getter);
 
-          // Getter and setter cannot be inlined because we don't know if null check semantic is
-          // preserved.
-          checkMethodIsKept(companionClass, getter);
-          checkMethodIsKept(companionClass, setter);
-          assertTrue(fieldSubject.getField().accessFlags.isPublic());
+          MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
+          checkMethodIsRemoved(companionClass, setter);
         });
   }