Strengthen parameter types

Change-Id: I469238b0fefbe632baaa4420c0d0289b4c2571dd
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 4f23975..0cd5a14 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -145,7 +145,7 @@
 
   @Override
   public String toString() {
-    return "Method " + holder + "." + name + " " + proto.toString();
+    return toSourceString();
   }
 
   public MethodReference asMethodReference() {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
index 65e9670..7e54571 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.InterfaceCollection.Builder;
 import com.android.tools.r8.utils.BooleanBox;
@@ -154,6 +155,16 @@
     return getOrCreateVariant(nullability().meet(nullability));
   }
 
+  public DexType toDexType(DexItemFactory dexItemFactory) {
+    if (type == dexItemFactory.objectType) {
+      DexType singleKnownInterface = getInterfaces().getSingleKnownInterface();
+      if (singleKnownInterface != null) {
+        return singleKnownInterface;
+      }
+    }
+    return type;
+  }
+
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 764167c..e34ab51 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -232,7 +232,7 @@
       assert outType.equalUpToNullability(castType);
 
       // Check soundness of null information.
-      assert inType.nullability().lessThanOrEqual(outType.nullability());
+      assert inType.nullability() == outType.nullability();
 
       // Since we cannot remove the cast the in-value must be different from null.
       assert !inType.isNullType();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 0957abc..7d12496 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1588,7 +1588,12 @@
 
     // If the cast is guaranteed to succeed and only there to ensure the program type checks, then
     // check if the program would still type check after removing the cast.
-    if (checkCast.isSafeCheckCast()) {
+    if (checkCast.isSafeCheckCast()
+        || checkCast
+            .getFirstOperand()
+            .getDynamicType(appViewWithLiveness)
+            .getDynamicUpperBoundType()
+            .lessThanOrEqualUpToNullability(castTypeLattice, appView)) {
       TypeElement useType =
           TypeUtils.computeUseType(appViewWithLiveness, context, checkCast.outValue());
       if (inTypeLattice.lessThanOrEqualUpToNullability(useType, appView)) {
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
index 897aaa8..7c196b1 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorApplicationFixer.java
@@ -93,9 +93,11 @@
           return method.toTypeSubstitutedMethod(
               methodReferenceAfterParameterRemoval,
               builder -> {
-                RewrittenPrototypeDescription prototypeChanges =
-                    graphLens.getPrototypeChanges(methodReferenceAfterParameterRemoval);
-                builder.apply(prototypeChanges.createParameterAnnotationsRemover(method));
+                if (graphLens.hasPrototypeChanges(methodReferenceAfterParameterRemoval)) {
+                  RewrittenPrototypeDescription prototypeChanges =
+                      graphLens.getPrototypeChanges(methodReferenceAfterParameterRemoval);
+                  builder.apply(prototypeChanges.createParameterAnnotationsRemover(method));
+                }
               });
         });
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
index 3ed1334..17865e4 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorGraphLens.java
@@ -35,7 +35,7 @@
   }
 
   public boolean hasPrototypeChanges(DexMethod method) {
-    return method != internalGetPreviousMethodSignature(method);
+    return prototypeChanges.containsKey(method);
   }
 
   public RewrittenPrototypeDescription getPrototypeChanges(DexMethod method) {
@@ -43,6 +43,10 @@
     return prototypeChanges.getOrDefault(method, RewrittenPrototypeDescription.none());
   }
 
+  public boolean isAffected(DexMethod method) {
+    return method != internalGetPreviousMethodSignature(method) || hasPrototypeChanges(method);
+  }
+
   @Override
   protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
     FieldLookupResult lookupResult = super.internalDescribeLookupField(previous);
@@ -61,8 +65,7 @@
   protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
       RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
     DexMethod previous = internalGetPreviousMethodSignature(method);
-    if (previous == method) {
-      assert !this.prototypeChanges.containsKey(method);
+    if (!hasPrototypeChanges(method)) {
       return prototypeChanges;
     }
     RewrittenPrototypeDescription newPrototypeChanges =
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 db9543f..f9bfde1 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
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 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.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
@@ -315,22 +316,32 @@
         continue;
       }
       DynamicType dynamicType = parameterState.asClassParameter().getDynamicType();
-      if (dynamicType.isUnknown()) {
-        continue;
-      }
       DexType staticType = method.getArgumentType(argumentIndex);
-      if (!WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, staticType)
-          .isUnknown()) {
-        continue;
+      if (shouldWidenDynamicTypeToUnknown(dynamicType, staticType)) {
+        methodState.setParameterState(
+            argumentIndex,
+            parameterState.mutableJoin(
+                appView,
+                new ConcreteClassTypeParameterState(AbstractValue.bottom(), DynamicType.unknown()),
+                staticType,
+                StateCloner.getIdentity()));
       }
-      methodState.setParameterState(
-          argumentIndex,
-          parameterState.mutableJoin(
-              appView,
-              new ConcreteClassTypeParameterState(AbstractValue.bottom(), DynamicType.unknown()),
-              staticType,
-              StateCloner.getIdentity()));
     }
     return !methodState.isEffectivelyUnknown();
   }
+
+  private boolean shouldWidenDynamicTypeToUnknown(DynamicType dynamicType, DexType staticType) {
+    if (dynamicType.isUnknown()) {
+      return false;
+    }
+    if (WideningUtils.widenDynamicNonReceiverType(appView, dynamicType, staticType).isUnknown()) {
+      return true;
+    }
+    TypeElement staticTypeElement = staticType.toTypeElement(appView);
+    TypeElement dynamicUpperBoundType = dynamicType.getDynamicUpperBoundType(staticTypeElement);
+    if (!dynamicUpperBoundType.lessThanOrEqual(staticTypeElement, appView)) {
+      return true;
+    }
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index 6b94973..0264a50 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -47,7 +47,11 @@
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.ints.IntArraySet;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
 import it.unimi.dsi.fastutil.ints.IntSet;
 import it.unimi.dsi.fastutil.ints.IntSets;
 import java.util.ArrayList;
@@ -62,6 +66,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.Consumer;
+import java.util.function.IntFunction;
 import java.util.function.IntPredicate;
 
 public class ArgumentPropagatorProgramOptimizer {
@@ -69,22 +74,43 @@
   static class AllowedPrototypeChanges {
 
     private static final AllowedPrototypeChanges EMPTY =
-        new AllowedPrototypeChanges(false, IntSets.EMPTY_SET);
+        new AllowedPrototypeChanges(false, Int2ReferenceMaps.emptyMap(), IntSets.EMPTY_SET);
 
     boolean canRewriteToVoid;
+    Int2ReferenceMap<DexType> newParameterTypes;
     IntSet removableParameterIndices;
 
-    AllowedPrototypeChanges(boolean canRewriteToVoid, IntSet removableParameterIndices) {
+    AllowedPrototypeChanges(
+        boolean canRewriteToVoid,
+        Int2ReferenceMap<DexType> newParameterTypes,
+        IntSet removableParameterIndices) {
       this.canRewriteToVoid = canRewriteToVoid;
+      this.newParameterTypes = newParameterTypes;
       this.removableParameterIndices = removableParameterIndices;
     }
 
     public static AllowedPrototypeChanges create(RewrittenPrototypeDescription prototypeChanges) {
-      return prototypeChanges.isEmpty()
-          ? empty()
-          : new AllowedPrototypeChanges(
-              prototypeChanges.hasBeenChangedToReturnVoid(),
-              prototypeChanges.getArgumentInfoCollection().getKeys());
+      if (prototypeChanges.isEmpty()) {
+        return empty();
+      }
+      Int2ReferenceMap<DexType> newParameterTypes = new Int2ReferenceOpenHashMap<>();
+      IntSet removableParameterIndices = new IntOpenHashSet();
+      prototypeChanges
+          .getArgumentInfoCollection()
+          .forEach(
+              (argumentIndex, argumentInfo) -> {
+                if (argumentInfo.isRemovedArgumentInfo()) {
+                  removableParameterIndices.add(argumentIndex);
+                } else {
+                  assert argumentInfo.isRewrittenTypeInfo();
+                  RewrittenTypeInfo rewrittenTypeInfo = argumentInfo.asRewrittenTypeInfo();
+                  newParameterTypes.put(argumentIndex, rewrittenTypeInfo.getNewType());
+                }
+              });
+      return new AllowedPrototypeChanges(
+          prototypeChanges.hasBeenChangedToReturnVoid(),
+          newParameterTypes,
+          removableParameterIndices);
     }
 
     public static AllowedPrototypeChanges empty() {
@@ -103,6 +129,7 @@
       }
       AllowedPrototypeChanges other = (AllowedPrototypeChanges) obj;
       return canRewriteToVoid == other.canRewriteToVoid
+          && newParameterTypes.equals(other.newParameterTypes)
           && removableParameterIndices.equals(other.removableParameterIndices);
     }
   }
@@ -308,13 +335,23 @@
               return;
             }
 
-            // Find the parameters that are constant or unused in all methods.
+            // Find the parameters that are either (i) the same constant, (ii) all unused, or (iii)
+            // all possible to strengthen to the same stronger type, in all methods.
+            Int2ReferenceMap<DexType> newParameterTypes = new Int2ReferenceOpenHashMap<>();
             IntSet removableVirtualMethodParametersInAllMethods = new IntArraySet();
             for (int parameterIndex = 1;
                 parameterIndex < signature.getProto().getArity() + 1;
                 parameterIndex++) {
-              if (canRemoveParameterFromVirtualMethods(parameterIndex, methods)) {
-                removableVirtualMethodParametersInAllMethods.add(parameterIndex);
+              if (!containsImmediateInterfaceOfInstantiatedLambda(methods)) {
+                if (canRemoveParameterFromVirtualMethods(methods, parameterIndex)) {
+                  removableVirtualMethodParametersInAllMethods.add(parameterIndex);
+                } else {
+                  DexType newParameterType =
+                      getNewParameterTypeForVirtualMethods(methods, parameterIndex);
+                  if (newParameterType != null) {
+                    newParameterTypes.put(parameterIndex, newParameterType);
+                  }
+                }
               }
             }
 
@@ -323,11 +360,13 @@
                 getReturnValueForVirtualMethods(signature, methods);
             boolean canRewriteVirtualMethodsToVoid = returnValueForVirtualMethods != null;
             if (canRewriteVirtualMethodsToVoid
+                || !newParameterTypes.isEmpty()
                 || !removableVirtualMethodParametersInAllMethods.isEmpty()) {
               allowedPrototypeChangesForVirtualMethods.put(
                   signature,
                   new AllowedPrototypeChanges(
                       canRewriteVirtualMethodsToVoid,
+                      newParameterTypes,
                       removableVirtualMethodParametersInAllMethods));
             }
 
@@ -403,18 +442,24 @@
       return returnValue;
     }
 
+    private boolean containsImmediateInterfaceOfInstantiatedLambda(ProgramMethodSet methods) {
+      for (ProgramMethod method : methods) {
+        DexProgramClass holder = method.getHolder();
+        if (holder.isInterface()) {
+          ObjectAllocationInfoCollection objectAllocationInfoCollection =
+              appView.appInfo().getObjectAllocationInfoCollection();
+          if (objectAllocationInfoCollection.isImmediateInterfaceOfInstantiatedLambda(holder)) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+
     private boolean canRemoveParameterFromVirtualMethods(
-        int parameterIndex, ProgramMethodSet methods) {
+        ProgramMethodSet methods, int parameterIndex) {
       for (ProgramMethod method : methods) {
         if (method.getDefinition().isAbstract()) {
-          DexProgramClass holder = method.getHolder();
-          if (holder.isInterface()) {
-            ObjectAllocationInfoCollection objectAllocationInfoCollection =
-                appView.appInfo().getObjectAllocationInfoCollection();
-            if (objectAllocationInfoCollection.isImmediateInterfaceOfInstantiatedLambda(holder)) {
-              return false;
-            }
-          }
           // OK, this parameter can be removed.
           continue;
         }
@@ -435,6 +480,26 @@
       return true;
     }
 
+    private DexType getNewParameterTypeForVirtualMethods(
+        ProgramMethodSet methods, int parameterIndex) {
+      DexType newParameterType = null;
+      for (ProgramMethod method : methods) {
+        if (method.getAccessFlags().isAbstract()) {
+          // OK, this parameter can have any type.
+          continue;
+        }
+        DexType newParameterTypeForMethod = getNewParameterType(method, parameterIndex);
+        if (newParameterTypeForMethod == null
+            || (newParameterType != null && newParameterType != newParameterTypeForMethod)) {
+          return null;
+        }
+        newParameterType = newParameterTypeForMethod;
+      }
+      assert newParameterType == null
+          || newParameterType != methods.getFirst().getArgumentType(parameterIndex);
+      return newParameterType;
+    }
+
     // Returns true if the class was changed as a result of argument propagation.
     private boolean visitClass(
         DexProgramClass clazz,
@@ -654,75 +719,154 @@
       }
 
       IntSet removableParameterIndices = allowedPrototypeChanges.removableParameterIndices;
-
+      Int2ReferenceMap<DexType> newParameterTypes = allowedPrototypeChanges.newParameterTypes;
       if (method.getAccessFlags().isAbstract()) {
-        // Treat the parameters as unused.
-        ArgumentInfoCollection.Builder removableParametersBuilder =
-            ArgumentInfoCollection.builder();
-        for (int removableParameterIndex : removableParameterIndices) {
-          removableParametersBuilder.addArgumentInfo(
-              removableParameterIndex,
-              RemovedArgumentInfo.builder()
-                  .setType(method.getArgumentType(removableParameterIndex))
-                  .build());
-        }
-        return RewrittenPrototypeDescription.create(
-            Collections.emptyList(),
-            computeReturnChangesForMethod(method, allowedPrototypeChanges.canRewriteToVoid),
-            removableParametersBuilder.build());
+        return computePrototypeChangesForAbstractVirtualMethod(
+            method,
+            allowedPrototypeChanges.canRewriteToVoid,
+            newParameterTypes,
+            removableParameterIndices);
       }
 
       RewrittenPrototypeDescription prototypeChanges =
           computePrototypeChangesForMethod(
               method,
               allowedPrototypeChanges.canRewriteToVoid,
+              newParameterTypes::get,
               removableParameterIndices::contains);
-      assert prototypeChanges.getArgumentInfoCollection().size()
+      assert prototypeChanges.getArgumentInfoCollection().numberOfRemovedArguments()
           == removableParameterIndices.size();
       return prototypeChanges;
     }
 
+    private RewrittenPrototypeDescription computePrototypeChangesForAbstractVirtualMethod(
+        ProgramMethod method,
+        boolean canRewriteToVoid,
+        Int2ReferenceMap<DexType> newParameterTypes,
+        IntSet removableParameterIndices) {
+
+      // Treat the parameters as unused.
+      ArgumentInfoCollection.Builder argumentInfoCollectionBuilder =
+          ArgumentInfoCollection.builder();
+      for (int argumentIndex = 0;
+          argumentIndex < method.getDefinition().getNumberOfArguments();
+          argumentIndex++) {
+        if (removableParameterIndices.contains(argumentIndex)) {
+          argumentInfoCollectionBuilder.addArgumentInfo(
+              argumentIndex,
+              RemovedArgumentInfo.builder().setType(method.getArgumentType(argumentIndex)).build());
+        } else if (newParameterTypes.containsKey(argumentIndex)) {
+          DexType newParameterType = newParameterTypes.get(argumentIndex);
+          argumentInfoCollectionBuilder.addArgumentInfo(
+              argumentIndex,
+              RewrittenTypeInfo.builder()
+                  .setCastType(newParameterType)
+                  .setOldType(method.getArgumentType(argumentIndex))
+                  .setNewType(newParameterType)
+                  .build());
+        }
+      }
+      return RewrittenPrototypeDescription.create(
+          Collections.emptyList(),
+          computeReturnChangesForMethod(method, canRewriteToVoid),
+          argumentInfoCollectionBuilder.build());
+    }
+
     private RewrittenPrototypeDescription computePrototypeChangesForMethod(ProgramMethod method) {
-      return computePrototypeChangesForMethod(method, true, parameterIndex -> true);
+      IntFunction<DexType> parameterIndexToParameterType =
+          appView.getKeepInfo(method).isParameterTypeStrengtheningAllowed(options)
+              ? parameterIndex -> getNewParameterType(method, parameterIndex)
+              : parameterIndex -> null;
+      return computePrototypeChangesForMethod(
+          method, true, parameterIndexToParameterType, parameterIndex -> true);
+    }
+
+    private DexType getNewParameterType(ProgramMethod method, int parameterIndex) {
+      if (!appView.getKeepInfo(method).isParameterTypeStrengtheningAllowed(options)) {
+        return null;
+      }
+      DexType staticType = method.getArgumentType(parameterIndex);
+      if (!staticType.isClassType()) {
+        return null;
+      }
+      DynamicType dynamicType =
+          method.getOptimizationInfo().getArgumentInfos().getDynamicType(parameterIndex);
+      if (dynamicType == null || dynamicType.isUnknown()) {
+        return null;
+      }
+      TypeElement staticTypeElement = staticType.toTypeElement(appView);
+      TypeElement dynamicUpperBoundType = dynamicType.getDynamicUpperBoundType(staticTypeElement);
+      assert dynamicUpperBoundType.lessThanOrEqual(staticTypeElement, appView);
+      assert dynamicUpperBoundType.isReferenceType();
+      if (dynamicUpperBoundType.isNullType()) {
+        return null;
+      }
+      if (dynamicUpperBoundType.isArrayType()) {
+        return null;
+      }
+      assert dynamicUpperBoundType.isClassType();
+      DexType newParameterType = dynamicUpperBoundType.asClassType().toDexType(dexItemFactory);
+      if (newParameterType == staticType) {
+        return null;
+      }
+      return AccessUtils.isAccessibleInSameContextsAs(newParameterType, staticType, appView)
+          ? newParameterType
+          : null;
     }
 
     private RewrittenPrototypeDescription computePrototypeChangesForMethod(
         ProgramMethod method,
         boolean allowToVoidRewriting,
+        IntFunction<DexType> newParameterTypes,
         IntPredicate removableParameterIndices) {
       return RewrittenPrototypeDescription.create(
           Collections.emptyList(),
           computeReturnChangesForMethod(method, allowToVoidRewriting),
-          computeParameterChangesForMethod(method, removableParameterIndices));
+          computeParameterChangesForMethod(method, newParameterTypes, removableParameterIndices));
     }
 
     private ArgumentInfoCollection computeParameterChangesForMethod(
-        ProgramMethod method, IntPredicate removableParameterIndices) {
+        ProgramMethod method,
+        IntFunction<DexType> newParameterTypes,
+        IntPredicate removableParameterIndices) {
       ConcreteCallSiteOptimizationInfo optimizationInfo =
           method.getOptimizationInfo().getArgumentInfos().asConcreteCallSiteOptimizationInfo();
       if (optimizationInfo == null) {
         return ArgumentInfoCollection.empty();
       }
 
-      ArgumentInfoCollection.Builder removableParametersBuilder = ArgumentInfoCollection.builder();
+      ArgumentInfoCollection.Builder parameterChangesBuilder = ArgumentInfoCollection.builder();
       for (int argumentIndex = method.getDefinition().getFirstNonReceiverArgumentIndex();
           argumentIndex < method.getDefinition().getNumberOfArguments();
           argumentIndex++) {
-        if (!removableParameterIndices.test(argumentIndex)) {
-          continue;
+        if (removableParameterIndices.test(argumentIndex)) {
+          AbstractValue abstractValue = optimizationInfo.getAbstractArgumentValue(argumentIndex);
+          if (abstractValue.isSingleValue()
+              && abstractValue.asSingleValue().isMaterializableInContext(appView, method)) {
+            parameterChangesBuilder.addArgumentInfo(
+                argumentIndex,
+                RemovedArgumentInfo.builder()
+                    .setSingleValue(abstractValue.asSingleValue())
+                    .setType(method.getArgumentType(argumentIndex))
+                    .build());
+            continue;
+          }
         }
-        AbstractValue abstractValue = optimizationInfo.getAbstractArgumentValue(argumentIndex);
-        if (abstractValue.isSingleValue()
-            && abstractValue.asSingleValue().isMaterializableInContext(appView, method)) {
-          removableParametersBuilder.addArgumentInfo(
+
+        DexType dynamicType = newParameterTypes.apply(argumentIndex);
+        if (dynamicType != null) {
+          DexType staticType = method.getArgumentType(argumentIndex);
+          assert dynamicType != staticType;
+          parameterChangesBuilder.addArgumentInfo(
               argumentIndex,
-              RemovedArgumentInfo.builder()
-                  .setSingleValue(abstractValue.asSingleValue())
-                  .setType(method.getArgumentType(argumentIndex))
+              RewrittenTypeInfo.builder()
+                  .setCastType(dynamicType)
+                  .setOldType(staticType)
+                  .setNewType(dynamicType)
                   .build());
         }
       }
-      return removableParametersBuilder.build();
+      return parameterChangesBuilder.build();
     }
 
     private RewrittenTypeInfo computeReturnChangesForMethod(
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
index 00749bb..cc8bc7f 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
@@ -46,6 +46,10 @@
     return backing.get(method);
   }
 
+  public T getFirst() {
+    return iterator().next();
+  }
+
   public boolean contains(DexMethod method) {
     return backing.containsKey(method);
   }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
index f1498b6..2472119 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
@@ -56,7 +56,8 @@
         .addKeepRules(
             "-alwaysinline class * { @"
                 + AlwaysInline.class.getTypeName()
-                + " !synthetic <methods>; }")
+                + " !synthetic <methods>; }",
+            "-noparametertypestrengthening class * { synthetic <methods>; }")
         .enableNeverClassInliningAnnotations()
         // TODO(b/120764902): MemberSubject.getOriginalName() is not working without the @NeverMerge
         //  annotation on DataAdapter.Observer.
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentInterfacesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentInterfacesTest.java
index 497e4df..1d6c93f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithDifferentInterfacesTest.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoParameterTypeStrengthening;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
 import org.junit.Test;
@@ -26,6 +27,7 @@
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoParameterTypeStrengtheningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addHorizontallyMergedClassesInspector(
@@ -78,7 +80,7 @@
 
   public static class Main {
     @NeverInline
-    public static void foo(I i) {
+    public static void foo(@NoParameterTypeStrengthening I i) {
       i.foo();
     }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java
index 9a5f503..29e7e0e 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClassesWithIdenticalInterfacesTest.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoParameterTypeStrengthening;
 import com.android.tools.r8.TestParameters;
 import org.junit.Test;
 
@@ -25,6 +26,7 @@
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoParameterTypeStrengtheningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addHorizontallyMergedClassesInspector(
             inspector ->
@@ -72,7 +74,7 @@
 
   public static class Main {
     @NeverInline
-    public static void foo(I i) {
+    public static void foo(@NoParameterTypeStrengthening I i) {
       i.foo();
     }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InterfacesVisibilityTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InterfacesVisibilityTest.java
index b03e236..508b10b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InterfacesVisibilityTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InterfacesVisibilityTest.java
@@ -32,6 +32,7 @@
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoParameterTypeStrengtheningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java
index 1ec170d..51b6b55 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideMergeAbsentTest.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoParameterTypeStrengthening;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
@@ -27,6 +28,7 @@
         .addKeepMainRule(Main.class)
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
+        .enableNoParameterTypeStrengtheningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addHorizontallyMergedClassesInspector(
@@ -79,7 +81,7 @@
 
   public static class Main {
     @NeverInline
-    public static void doI(J i) {
+    public static void doI(@NoParameterTypeStrengthening J i) {
       i.m();
     }
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/InterfacesVisibilityTestClasses.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/InterfacesVisibilityTestClasses.java
index d5a5c14..c857046 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/InterfacesVisibilityTestClasses.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/testclasses/InterfacesVisibilityTestClasses.java
@@ -7,12 +7,13 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoParameterTypeStrengthening;
 import com.android.tools.r8.NoVerticalClassMerging;
 
 public class InterfacesVisibilityTestClasses {
   public static class Invoker {
     @NeverInline
-    public static void invokeFoo(PackagePrivateInterface i) {
+    public static void invokeFoo(@NoParameterTypeStrengthening PackagePrivateInterface i) {
       i.foo();
     }
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index fba166a..62a44a1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -326,7 +326,11 @@
     m = clazz.method("int", "inlinable", ImmutableList.of("inlining.A"));
     assertCounters(INLINABLE, INLINABLE, countInvokes(inspector, m));
 
-    m = clazz.method("int", "notInlinable", ImmutableList.of("inlining.A"));
+    m =
+        clazz.method(
+            "int",
+            "notInlinable",
+            ImmutableList.of("inlining." + (allowAccessModification ? "B" : "A")));
     assertCounters(INLINABLE, NEVER_INLINABLE, countInvokes(inspector, m));
 
     m = clazz.method("int", "notInlinableDueToMissingNpe", ImmutableList.of("inlining.A"));
@@ -338,14 +342,16 @@
         NEVER_INLINABLE,
         countInvokes(inspector, m));
 
-    m = clazz.method("int", "notInlinableOnThrow", ImmutableList.of("java.lang.Throwable"));
+    m =
+        clazz.method(
+            "int", "notInlinableOnThrow", ImmutableList.of("java.lang.IllegalArgumentException"));
     assertCounters(ALWAYS_INLINABLE, NEVER_INLINABLE, countInvokes(inspector, m));
 
     m =
         clazz.method(
             "int",
             "notInlinableDueToMissingNpeBeforeThrow",
-            ImmutableList.of("java.lang.Throwable"));
+            ImmutableList.of("java.lang.IllegalArgumentException"));
     assertCounters(ALWAYS_INLINABLE, NEVER_INLINABLE * 2, countInvokes(inspector, m));
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
index 5811471..0cd06dc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoParameterTypeStrengthening;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -43,6 +44,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeDirectPositiveTest.class)
         .addKeepMainRule(MAIN)
+        .enableNoParameterTypeStrengtheningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
@@ -107,6 +109,7 @@
     }
 
     @NeverInline
+    @NoParameterTypeStrengthening
     private void test(Base arg) {
       if (arg instanceof Sub1) {
         System.out.println("Sub1");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
index a32daad..2dafd1b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoParameterTypeStrengthening;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -42,6 +43,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeStaticPositiveTest.class)
         .addKeepMainRule(MAIN)
+        .enableNoParameterTypeStrengtheningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .enableInliningAnnotations()
         .addOptionsModification(
@@ -95,6 +97,7 @@
       test(new Sub1()); // calls test with Sub1.
     }
 
+    @NoParameterTypeStrengthening
     @NeverInline
     static void test(Base arg) {
       if (arg instanceof Sub1) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
index 99a3ddf..382be79 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoParameterTypeStrengthening;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -43,6 +44,7 @@
     testForR8(parameters.getBackend())
         .addInnerClasses(InvokeVirtualPositiveTest.class)
         .addKeepMainRule(MAIN)
+        .enableNoParameterTypeStrengtheningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableInliningAnnotations()
@@ -110,6 +112,7 @@
   @NoVerticalClassMerging
   @NeverClassInline
   static class A {
+    @NoParameterTypeStrengthening
     @NeverInline
     void m(Base arg) {
       if (arg instanceof Sub1) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsMixedTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsMixedTest.java
index 78a0882..a6f80c4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsMixedTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsMixedTest.java
@@ -78,8 +78,11 @@
           Assert.assertTrue(
               method.getFinalName().equals("main")
                   || (method.getFinalSignature().parameters.length == 1
-                  && (method.getFinalSignature().parameters[0].equals("int")
-                      || method.getFinalSignature().parameters[0].equals("java.lang.Object"))));
+                      && (method.getFinalSignature().parameters[0].equals("int")
+                          || method
+                              .getFinalSignature()
+                              .parameters[0]
+                              .equals("java.lang.Integer"))));
         });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
index c10d1db..fdefaac 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoParameterTypeStrengthening;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -45,6 +46,7 @@
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
+        .enableNoParameterTypeStrengtheningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .setMinApi(parameters.getApiLevel())
         .compile()
@@ -82,6 +84,7 @@
     }
 
     @NeverInline
+    @NoParameterTypeStrengthening
     private static void indirection(I obj) {
       obj.m();
     }
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceDefaultBridgeTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceDefaultBridgeTest.java
index e11bdfe..4036e4a 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceDefaultBridgeTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceDefaultBridgeTest.java
@@ -11,6 +11,7 @@
 
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoParameterTypeStrengthening;
 import com.android.tools.r8.NoVerticalClassMerging;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -49,6 +50,7 @@
         .addKeepClassAndMembersRules(I.class)
         .enableInliningAnnotations()
         .enableNoHorizontalClassMergingAnnotations()
+        .enableNoParameterTypeStrengtheningAnnotations()
         .enableNoVerticalClassMergingAnnotations()
         .run(parameters.getRuntime(), newMainTypeName)
         .assertSuccessWithOutputLines("I::foo")
@@ -85,6 +87,7 @@
     }
 
     @NeverInline
+    @NoParameterTypeStrengthening
     private static void callJ(J j) {
       j.foo();
     }
diff --git a/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ParameterTypeStrengtheningTest.java b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ParameterTypeStrengtheningTest.java
new file mode 100644
index 0000000..91abde9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/argumentpropagation/ParameterTypeStrengtheningTest.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2022, 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.optimize.argumentpropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ParameterTypeStrengtheningTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        // TODO(b/173398086): uniqueMethodWithName() does not work with argument changes.
+        .noMinification()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject mainClassSubject = inspector.clazz(Main.class);
+              assertThat(mainClassSubject, isPresent());
+
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+
+              ClassSubject bClassSubject = inspector.clazz(B.class);
+              assertThat(bClassSubject, isPresent());
+
+              // Method testA(I) should be rewritten to testA(A).
+              MethodSubject testAMethodSubject = mainClassSubject.uniqueMethodWithName("testA");
+              assertThat(testAMethodSubject, isPresent());
+              assertEquals(
+                  aClassSubject.getFinalName(),
+                  testAMethodSubject.getProgramMethod().getParameter(0).getTypeName());
+
+              // Method testB(I) should be rewritten to testB(B).
+              MethodSubject testBMethodSubject = mainClassSubject.uniqueMethodWithName("testB");
+              assertThat(testBMethodSubject, isPresent());
+              assertEquals(
+                  bClassSubject.getFinalName(),
+                  testBMethodSubject.getProgramMethod().getParameter(0).getTypeName());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A", "B");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      testA(new A());
+      testB(getB());
+    }
+
+    @NeverInline
+    static void testA(I i) {
+      i.m();
+    }
+
+    @NeverInline
+    static void testB(I i) {
+      i.m();
+    }
+
+    @NeverInline
+    static I getB() {
+      return new B();
+    }
+  }
+
+  interface I {
+
+    void m();
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class A implements I {
+
+    @Override
+    public void m() {
+      System.out.println("A");
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class B implements I {
+
+    @Override
+    public void m() {
+      System.out.println("B");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
index 873b1e4..eeb91af 100644
--- a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static org.hamcrest.CoreMatchers.containsString;
@@ -141,7 +142,11 @@
             .inspector();
 
     ClassSubject superInterface1 = inspector.clazz(B112452064SuperInterface1.class);
-    assertThat(superInterface1, isPresentAndRenamed());
+    if (enableUnusedInterfaceRemoval && enableVerticalClassMerging) {
+      assertThat(superInterface1, isAbsent());
+    } else {
+      assertThat(superInterface1, isPresentAndRenamed());
+    }
     MethodSubject foo = superInterface1.uniqueMethodWithName("foo");
     assertThat(foo, not(isPresent()));
     ClassSubject superInterface2 = inspector.clazz(B112452064SuperInterface2.class);
diff --git a/tools/git_sync_cl_chain.py b/tools/git_sync_cl_chain.py
index ea67f63..09da33e 100755
--- a/tools/git_sync_cl_chain.py
+++ b/tools/git_sync_cl_chain.py
@@ -44,6 +44,9 @@
                     help='Delete closed branches',
                     choices=['y', 'n', 'ask'],
                     default='ask')
+  result.add_option('--from_branch', '-f',
+                    help='Uppermost upstream to sync from',
+                    default='main')
   result.add_option('--leave_upstream', '--leave-upstream',
                     help='To not update the upstream of the first open branch',
                     action='store_true')
@@ -81,14 +84,14 @@
         break
     assert current_branch is not None
 
-    if current_branch.upstream == None:
+    if is_root_branch(current_branch, options):
       print('Nothing to sync')
       return
 
     stack = []
     while current_branch:
       stack.append(current_branch)
-      if current_branch.upstream is None:
+      if is_root_branch(current_branch, options):
         break
       current_branch = get_branch_with_name(current_branch.upstream, branches)
 
@@ -169,6 +172,9 @@
 def get_status_for_current_branch():
   return utils.RunCmd(['git', 'cl', 'status', '--field', 'status'], quiet=True)[0].strip()
 
+def is_root_branch(branch, options):
+  return branch == options.from_branch or branch.upstream is None
+
 def pull_for_current_branch(branch, options):
   if branch.name == 'main' and options.skip_main:
     return