Compute feature placement of synthetics based on their context.

Fixes: 180086194
Change-Id: I7c689b37131bd43b72635f0a39b427f2432c1b58
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 8f56827..614ac4d 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -467,7 +467,7 @@
 
       // Pull out the classes that should go into feature splits.
       Map<FeatureSplit, Set<DexProgramClass>> featureSplitClasses =
-          classToFeatureSplitMap.getFeatureSplitClasses(classes);
+          classToFeatureSplitMap.getFeatureSplitClasses(classes, appView.getSyntheticItems());
       if (featureSplitClasses.size() > 0) {
         for (Set<DexProgramClass> featureClasses : featureSplitClasses.values()) {
           classes.removeAll(featureClasses);
@@ -475,14 +475,17 @@
       }
       List<DexProgramClass> toRemove = new ArrayList<>();
       for (DexProgramClass dexProgramClass : classes) {
-        Collection<DexProgramClass> synthesizedFrom = dexProgramClass.getSynthesizedFrom();
-        if (!synthesizedFrom.isEmpty()) {
-          DexProgramClass from = Iterables.getFirst(synthesizedFrom, null);
-          FeatureSplit featureSplit = classToFeatureSplitMap.getFeatureSplit(from);
-          if (!featureSplit.isBase()) {
-            Set<DexProgramClass> dexProgramClasses = featureSplitClasses.get(featureSplit);
-            dexProgramClasses.add(dexProgramClass);
-            toRemove.add(dexProgramClass);
+        if (appView.getSyntheticItems().isLegacySyntheticClass(dexProgramClass)) {
+          Collection<DexProgramClass> synthesizedFrom = dexProgramClass.getSynthesizedFrom();
+          if (!synthesizedFrom.isEmpty()) {
+            DexProgramClass from = Iterables.getFirst(synthesizedFrom, null);
+            FeatureSplit featureSplit =
+                classToFeatureSplitMap.getFeatureSplit(from, appView.getSyntheticItems());
+            if (!featureSplit.isBase()) {
+              Set<DexProgramClass> dexProgramClasses = featureSplitClasses.get(featureSplit);
+              dexProgramClasses.add(dexProgramClass);
+              toRemove.add(dexProgramClass);
+            }
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
index 1ea1595..d96643d 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -17,11 +17,15 @@
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.Sets;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 
@@ -88,9 +92,9 @@
     return result;
   }
 
-  public int compareFeatureSplitsForDexTypes(DexType a, DexType b) {
-    FeatureSplit featureSplitA = getFeatureSplit(a);
-    FeatureSplit featureSplitB = getFeatureSplit(b);
+  public int compareFeatureSplitsForDexTypes(DexType a, DexType b, SyntheticItems syntheticItems) {
+    FeatureSplit featureSplitA = getFeatureSplit(a, syntheticItems);
+    FeatureSplit featureSplitB = getFeatureSplit(b, syntheticItems);
     assert featureSplitA != null;
     assert featureSplitB != null;
     if (featureSplitA == featureSplitB) {
@@ -109,10 +113,10 @@
   }
 
   public Map<FeatureSplit, Set<DexProgramClass>> getFeatureSplitClasses(
-      Set<DexProgramClass> classes) {
+      Set<DexProgramClass> classes, SyntheticItems syntheticItems) {
     Map<FeatureSplit, Set<DexProgramClass>> result = new IdentityHashMap<>();
     for (DexProgramClass clazz : classes) {
-      FeatureSplit featureSplit = getFeatureSplit(clazz);
+      FeatureSplit featureSplit = getFeatureSplit(clazz, syntheticItems);
       if (featureSplit != null && !featureSplit.isBase()) {
         result.computeIfAbsent(featureSplit, ignore -> Sets.newIdentityHashSet()).add(clazz);
       }
@@ -120,41 +124,61 @@
     return result;
   }
 
-  public FeatureSplit getFeatureSplit(ProgramDefinition clazz) {
-    return getFeatureSplit(clazz.getContextType());
+  public FeatureSplit getFeatureSplit(ProgramDefinition clazz, SyntheticItems syntheticItems) {
+    return getFeatureSplit(clazz.getContextType(), syntheticItems);
   }
 
-  public FeatureSplit getFeatureSplit(DexType type) {
+  public FeatureSplit getFeatureSplit(DexType type, SyntheticItems syntheticItems) {
+    Collection<DexType> contexts = syntheticItems.getSynthesizingContexts(type);
+    if (!contexts.isEmpty()) {
+      Iterator<DexType> iterator = contexts.iterator();
+      DexType context = iterator.next();
+      FeatureSplit feature = classToFeatureSplitMap.getOrDefault(context, FeatureSplit.BASE);
+      assert verifyConsistentFeatureContexts(iterator, feature);
+      return feature;
+    }
     return classToFeatureSplitMap.getOrDefault(type, FeatureSplit.BASE);
   }
 
+  private boolean verifyConsistentFeatureContexts(
+      Iterator<DexType> contextIterator, FeatureSplit feature) {
+    while (contextIterator.hasNext()) {
+      assert feature
+          == classToFeatureSplitMap.getOrDefault(contextIterator.next(), FeatureSplit.BASE);
+    }
+    return true;
+  }
+
   public boolean isEmpty() {
     return classToFeatureSplitMap.isEmpty();
   }
 
-  public boolean isInBase(DexProgramClass clazz) {
-    return getFeatureSplit(clazz).isBase();
+  public boolean isInBase(DexProgramClass clazz, SyntheticItems syntheticItems) {
+    return getFeatureSplit(clazz, syntheticItems).isBase();
   }
 
-  public boolean isInBaseOrSameFeatureAs(DexProgramClass clazz, ProgramDefinition context) {
-    FeatureSplit split = getFeatureSplit(clazz);
-    return split.isBase() || split == getFeatureSplit(context);
+  public boolean isInBaseOrSameFeatureAs(
+      DexProgramClass clazz, ProgramDefinition context, SyntheticItems syntheticItems) {
+    FeatureSplit split = getFeatureSplit(clazz, syntheticItems);
+    return split.isBase() || split == getFeatureSplit(context, syntheticItems);
   }
 
-  public boolean isInFeature(DexProgramClass clazz) {
-    return !isInBase(clazz);
+  public boolean isInFeature(DexProgramClass clazz, SyntheticItems syntheticItems) {
+    return !isInBase(clazz, syntheticItems);
   }
 
-  public boolean isInSameFeatureOrBothInBase(ProgramMethod a, ProgramMethod b) {
-    return isInSameFeatureOrBothInBase(a.getHolder(), b.getHolder());
+  public boolean isInSameFeatureOrBothInBase(
+      ProgramMethod a, ProgramMethod b, SyntheticItems syntheticItems) {
+    return isInSameFeatureOrBothInBase(a.getHolder(), b.getHolder(), syntheticItems);
   }
 
-  public boolean isInSameFeatureOrBothInBase(DexProgramClass a, DexProgramClass b) {
-    return getFeatureSplit(a) == getFeatureSplit(b);
+  public boolean isInSameFeatureOrBothInBase(
+      DexProgramClass a, DexProgramClass b, SyntheticItems syntheticItems) {
+    return getFeatureSplit(a, syntheticItems) == getFeatureSplit(b, syntheticItems);
   }
 
-  public boolean isInSameFeatureOrBothInBase(DexType a, DexType b) {
-    return getFeatureSplit(a) == getFeatureSplit(b);
+  public boolean isInSameFeatureOrBothInBase(DexType a, DexType b, SyntheticItems syntheticItems) {
+    return getFeatureSplit(a, syntheticItems) == getFeatureSplit(b, syntheticItems);
   }
 
   public ClassToFeatureSplitMap rewrittenWithLens(GraphLens lens) {
@@ -187,4 +211,14 @@
         });
     return classToFeatureSplitMapAfterPruning;
   }
+
+  // Static helpers to avoid verbose predicates.
+
+  private static ClassToFeatureSplitMap getMap(AppView<AppInfoWithLiveness> appView) {
+    return appView.appInfo().getClassToFeatureSplitMap();
+  }
+
+  public static boolean isInFeature(DexProgramClass clazz, AppView<AppInfoWithLiveness> appView) {
+    return getMap(appView).isInFeature(clazz, appView.getSyntheticItems());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index f872a56..51ff1e2 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.OptionalBool;
 
 /**
@@ -18,16 +19,21 @@
       DexClass clazz,
       ProgramDefinition context,
       AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return isClassAccessible(clazz, context, appView.appInfo().getClassToFeatureSplitMap());
+    return isClassAccessible(
+        clazz, context, appView.appInfo().getClassToFeatureSplitMap(), appView.getSyntheticItems());
   }
 
   public static OptionalBool isClassAccessible(
-      DexClass clazz, ProgramDefinition context, ClassToFeatureSplitMap classToFeatureSplitMap) {
+      DexClass clazz,
+      ProgramDefinition context,
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      SyntheticItems syntheticItems) {
     if (!clazz.isPublic() && !clazz.getType().isSamePackage(context.getContextType())) {
       return OptionalBool.FALSE;
     }
     if (clazz.isProgramClass()
-        && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(clazz.asProgramClass(), context)) {
+        && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(
+            clazz.asProgramClass(), context, syntheticItems)) {
       return OptionalBool.UNKNOWN;
     }
     return OptionalBool.TRUE;
@@ -60,7 +66,11 @@
       AppInfoWithClassHierarchy appInfo) {
     AccessFlags<?> memberFlags = member.getDefinition().getAccessFlags();
     OptionalBool classAccessibility =
-        isClassAccessible(initialResolutionHolder, context, appInfo.getClassToFeatureSplitMap());
+        isClassAccessible(
+            initialResolutionHolder,
+            context,
+            appInfo.getClassToFeatureSplitMap(),
+            appInfo.getSyntheticItems());
     if (classAccessibility.isFalse()) {
       return OptionalBool.FALSE;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
index bb61928..2cfb4d9 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -105,12 +105,14 @@
     }
     // Check if service is defined feature
     DexProgramClass serviceClass = appView.definitionForProgramType(serviceType);
-    if (serviceClass != null && classToFeatureSplitMap.isInFeature(serviceClass)) {
+    if (serviceClass != null
+        && classToFeatureSplitMap.isInFeature(serviceClass, appView.getSyntheticItems())) {
       return true;
     }
     for (DexType implementationType : featureImplementations.get(FeatureSplit.BASE)) {
       DexProgramClass implementationClass = appView.definitionForProgramType(implementationType);
-      if (implementationClass != null && classToFeatureSplitMap.isInFeature(implementationClass)) {
+      if (implementationClass != null
+          && classToFeatureSplitMap.isInFeature(implementationClass, appView.getSyntheticItems())) {
         return true;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
index 9e4dd2b..20caa50 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
@@ -19,6 +19,9 @@
 
   @Override
   public FeatureSplit getMergeKey(DexProgramClass clazz) {
-    return appView.appInfo().getClassToFeatureSplitMap().getFeatureSplit(clazz);
+    return appView
+        .appInfo()
+        .getClassToFeatureSplitMap()
+        .getFeatureSplit(clazz, appView.getSyntheticItems());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 969b5d5..33e414f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -38,6 +38,7 @@
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
@@ -152,10 +153,11 @@
       return false;
     }
 
+    SyntheticItems syntheticItems = appView.getSyntheticItems();
     ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
-    if (!classToFeatureSplitMap.isInSameFeatureOrBothInBase(singleTarget, method)) {
+    if (!classToFeatureSplitMap.isInSameFeatureOrBothInBase(singleTarget, method, syntheticItems)) {
       // Still allow inlining if we inline from the base into a feature.
-      if (!classToFeatureSplitMap.isInBase(singleTarget.getHolder())) {
+      if (!classToFeatureSplitMap.isInBase(singleTarget.getHolder(), syntheticItems)) {
         whyAreYouNotInliningReporter.reportInliningAcrossFeatureSplit();
         return false;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 8dc0dd3..0f12d78 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClasspathMethod;
 import com.android.tools.r8.graph.Code;
@@ -1287,7 +1288,7 @@
         code -> {
           ProgramMethod context = code.context();
           assert !context.getDefinition().getCode().isOutlineCode();
-          if (appView.appInfo().getClassToFeatureSplitMap().isInFeature(context.getHolder())) {
+          if (ClassToFeatureSplitMap.isInFeature(context.getHolder(), appView)) {
             return;
           }
           for (BasicBlock block : code.blocks) {
@@ -1306,7 +1307,7 @@
   public void identifyOutlineSites(IRCode code) {
     ProgramMethod context = code.context();
     assert !context.getDefinition().getCode().isOutlineCode();
-    assert !appView.appInfo().getClassToFeatureSplitMap().isInFeature(context.getHolder());
+    assert !ClassToFeatureSplitMap.isInFeature(context.getHolder(), appView);
     for (BasicBlock block : code.blocks) {
       new OutlineSiteIdentifier(context, block).process();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index afdf56a..289b2ba 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -246,7 +246,8 @@
 
     // Make sure the (base) type is visible.
     ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
-    if (AccessControl.isClassAccessible(baseClass, context, classToFeatureSplitMap)
+    if (AccessControl.isClassAccessible(
+            baseClass, context, classToFeatureSplitMap, appView.getSyntheticItems())
         .isPossiblyFalse()) {
       return;
     }
@@ -254,7 +255,8 @@
     // If the type is guaranteed to be visible, it must be in the same feature as the current method
     // or in the base.
     assert !baseClass.isProgramClass()
-        || classToFeatureSplitMap.isInBaseOrSameFeatureAs(baseClass.asProgramClass(), context);
+        || classToFeatureSplitMap.isInBaseOrSameFeatureAs(
+            baseClass.asProgramClass(), context, appView.getSyntheticItems());
 
     consumer.accept(type, baseClass);
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 4963f21..afc9e50 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -357,7 +357,7 @@
 
     if (!appInfo
         .getClassToFeatureSplitMap()
-        .isInSameFeatureOrBothInBase(sourceClass, targetClass)) {
+        .isInSameFeatureOrBothInBase(sourceClass, targetClass, appView.getSyntheticItems())) {
       return false;
     }
     if (appView.appServices().allServiceTypes().contains(sourceClass.type)
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
index 9520d57..f6577d3 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -66,7 +66,10 @@
   public abstract C getHolder();
 
   final HashCode computeHash(
-      RepresentativeMap map, boolean intermediate, ClassToFeatureSplitMap classToFeatureSplitMap) {
+      RepresentativeMap map,
+      boolean intermediate,
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      SyntheticItems syntheticItems) {
     Hasher hasher = Hashing.murmur3_128().newHasher();
     if (getKind().isFixedSuffixSynthetic) {
       // Fixed synthetics are non-shareable. Its unique type is used as the hash key.
@@ -81,7 +84,7 @@
     if (!classToFeatureSplitMap.isEmpty()) {
       hasher.putInt(
           classToFeatureSplitMap
-              .getFeatureSplit(getContext().getSynthesizingContextType())
+              .getFeatureSplit(getContext().getSynthesizingContextType(), syntheticItems)
               .hashCode());
     }
 
@@ -95,15 +98,17 @@
       D other,
       boolean includeContext,
       GraphLens graphLens,
-      ClassToFeatureSplitMap classToFeatureSplitMap) {
-    return compareTo(other, includeContext, graphLens, classToFeatureSplitMap) == 0;
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      SyntheticItems syntheticItems) {
+    return compareTo(other, includeContext, graphLens, classToFeatureSplitMap, syntheticItems) == 0;
   }
 
   int compareTo(
       D other,
       boolean includeContext,
       GraphLens graphLens,
-      ClassToFeatureSplitMap classToFeatureSplitMap) {
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      SyntheticItems syntheticItems) {
     DexType thisType = getHolder().getType();
     DexType otherType = other.getHolder().getType();
     if (getKind().isFixedSuffixSynthetic) {
@@ -120,10 +125,10 @@
       DexType synthesizingContextType = this.getContext().getSynthesizingContextType();
       DexType otherSynthesizingContextType = other.getContext().getSynthesizingContextType();
       if (!classToFeatureSplitMap.isInSameFeatureOrBothInBase(
-          synthesizingContextType, otherSynthesizingContextType)) {
+          synthesizingContextType, otherSynthesizingContextType, syntheticItems)) {
         int order =
             classToFeatureSplitMap.compareFeatureSplitsForDexTypes(
-                synthesizingContextType, otherSynthesizingContextType);
+                synthesizingContextType, otherSynthesizingContextType, syntheticItems);
         assert order != 0;
         return order;
       }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 60dca8f..65de13f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -203,9 +203,11 @@
     public int compareToIncludingContext(
         EquivalenceGroup<T> other,
         GraphLens graphLens,
-        ClassToFeatureSplitMap classToFeatureSplitMap) {
+        ClassToFeatureSplitMap classToFeatureSplitMap,
+        SyntheticItems syntheticItems) {
       return getRepresentative()
-          .compareTo(other.getRepresentative(), true, graphLens, classToFeatureSplitMap);
+          .compareTo(
+              other.getRepresentative(), true, graphLens, classToFeatureSplitMap, syntheticItems);
     }
 
     @Override
@@ -219,11 +221,14 @@
   }
 
   private final InternalOptions options;
-  private final CommittedSyntheticsCollection synthetics;
+  private final SyntheticItems synthetics;
+  private final CommittedSyntheticsCollection committed;
 
-  SyntheticFinalization(InternalOptions options, CommittedSyntheticsCollection synthetics) {
+  SyntheticFinalization(
+      InternalOptions options, SyntheticItems synthetics, CommittedSyntheticsCollection committed) {
     this.options = options;
     this.synthetics = synthetics;
+    this.committed = committed;
   }
 
   public static void finalize(AppView<AppInfo> appView) {
@@ -269,8 +274,8 @@
       application =
           buildLensAndProgram(
               appView,
-              computeEquivalences(appView, synthetics.getNonLegacyMethods().values(), generators),
-              computeEquivalences(appView, synthetics.getNonLegacyClasses().values(), generators),
+              computeEquivalences(appView, committed.getNonLegacyMethods().values(), generators),
+              computeEquivalences(appView, committed.getNonLegacyClasses().values(), generators),
               lensBuilder,
               (clazz, reference) -> {
                 finalSyntheticProgramDefinitions.add(clazz);
@@ -291,7 +296,7 @@
     assert appView.appInfo().getMainDexInfo() == mainDexInfo;
 
     Set<DexType> prunedSynthetics = Sets.newIdentityHashSet();
-    synthetics.forEachNonLegacyItem(
+    committed.forEachNonLegacyItem(
         reference -> {
           DexType type = reference.getHolder();
           if (!finalMethods.containsKey(type) && !finalClasses.containsKey(type)) {
@@ -304,7 +309,7 @@
             SyntheticItems.INVALID_ID_AFTER_SYNTHETIC_FINALIZATION,
             application,
             new CommittedSyntheticsCollection(
-                synthetics.getLegacyTypes(), finalMethods, finalClasses),
+                committed.getLegacyTypes(), finalMethods, finalClasses),
             ImmutableList.of()),
         lensBuilder.build(appView.graphLens(), appView.dexItemFactory()),
         PrunedItems.builder()
@@ -330,18 +335,19 @@
             intermediate,
             appView.dexItemFactory(),
             appView.graphLens(),
-            classToFeatureSplitMap);
+            classToFeatureSplitMap,
+            synthetics);
     return computeActualEquivalences(
         potentialEquivalences, generators, appView, intermediate, classToFeatureSplitMap);
   }
 
   private boolean isNotSyntheticType(DexType type) {
-    return !synthetics.containsNonLegacyType(type);
+    return !committed.containsNonLegacyType(type);
   }
 
   private boolean verifyNoNestedSynthetics() {
     // Check that a context is never itself synthetic class.
-    synthetics.forEachNonLegacyItem(
+    committed.forEachNonLegacyItem(
         item -> {
           assert isNotSyntheticType(item.getContext().getSynthesizingContextType());
         });
@@ -368,7 +374,7 @@
       DexApplication application, List<DexProgramClass> finalSyntheticClasses) {
     ListMultimap<DexProgramClass, DexProgramClass> originalToSynthesized =
         ArrayListMultimap.create();
-    for (DexType type : synthetics.getLegacyTypes()) {
+    for (DexType type : committed.getLegacyTypes()) {
       DexProgramClass clazz = DexProgramClass.asProgramClassOrNull(application.definitionFor(type));
       if (clazz != null) {
         for (DexProgramClass origin : clazz.getSynthesizedFrom()) {
@@ -670,10 +676,12 @@
     potentialEquivalences.forEach(
         members -> {
           List<List<T>> groups =
-              groupEquivalent(members, intermediate, appView.graphLens(), classToFeatureSplitMap);
+              groupEquivalent(
+                  members, intermediate, appView.graphLens(), classToFeatureSplitMap, synthetics);
           for (List<T> group : groups) {
             T representative =
-                findDeterministicRepresentative(group, appView.graphLens(), classToFeatureSplitMap);
+                findDeterministicRepresentative(
+                    group, appView.graphLens(), classToFeatureSplitMap, synthetics);
             // The representative is required to be the first element of the group.
             group.remove(representative);
             group.add(0, representative);
@@ -691,7 +699,8 @@
           // representative which is equal to 'context' here (see assert below).
           groups.sort(
               (a, b) ->
-                  a.compareToIncludingContext(b, appView.graphLens(), classToFeatureSplitMap));
+                  a.compareToIncludingContext(
+                      b, appView.graphLens(), classToFeatureSplitMap, synthetics));
           for (int i = 0; i < groups.size(); i++) {
             EquivalenceGroup<T> group = groups.get(i);
             assert group
@@ -702,7 +711,11 @@
             // of the synthetic name will be non-deterministic between the two.
             assert i == 0
                 || checkGroupsAreDistinct(
-                    groups.get(i - 1), group, appView.graphLens(), classToFeatureSplitMap);
+                    groups.get(i - 1),
+                    group,
+                    appView.graphLens(),
+                    classToFeatureSplitMap,
+                    synthetics);
             SyntheticKind kind = group.members.get(0).getKind();
             DexType representativeType =
                 createExternalType(kind, externalSyntheticTypePrefix, generators, appView);
@@ -716,14 +729,15 @@
       List<T> potentialEquivalence,
       boolean intermediate,
       GraphLens graphLens,
-      ClassToFeatureSplitMap classToFeatureSplitMap) {
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      SyntheticItems syntheticItems) {
     List<List<T>> groups = new ArrayList<>();
     // Each other member is in a shared group if it is actually equivalent to the first member.
     for (T synthetic : potentialEquivalence) {
       boolean requireNewGroup = true;
       for (List<T> group : groups) {
         if (synthetic.isEquivalentTo(
-            group.get(0), intermediate, graphLens, classToFeatureSplitMap)) {
+            group.get(0), intermediate, graphLens, classToFeatureSplitMap, syntheticItems)) {
           requireNewGroup = false;
           group.add(synthetic);
           break;
@@ -742,15 +756,20 @@
       EquivalenceGroup<T> g1,
       EquivalenceGroup<T> g2,
       GraphLens graphLens,
-      ClassToFeatureSplitMap classToFeatureSplitMap) {
-    int order = g1.compareToIncludingContext(g2, graphLens, classToFeatureSplitMap);
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      SyntheticItems syntheticItems) {
+    int order = g1.compareToIncludingContext(g2, graphLens, classToFeatureSplitMap, syntheticItems);
     assert order != 0;
-    assert order != g2.compareToIncludingContext(g1, graphLens, classToFeatureSplitMap);
+    assert order
+        != g2.compareToIncludingContext(g1, graphLens, classToFeatureSplitMap, syntheticItems);
     return true;
   }
 
   private static <T extends SyntheticDefinition<?, T, ?>> T findDeterministicRepresentative(
-      List<T> members, GraphLens graphLens, ClassToFeatureSplitMap classToFeatureSplitMap) {
+      List<T> members,
+      GraphLens graphLens,
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      SyntheticItems syntheticItems) {
     // Pick a deterministic member as representative.
     T smallest = members.get(0);
     for (int i = 1; i < members.size(); i++) {
@@ -794,7 +813,8 @@
           boolean intermediate,
           DexItemFactory factory,
           GraphLens graphLens,
-          ClassToFeatureSplitMap classToFeatureSplitMap) {
+          ClassToFeatureSplitMap classToFeatureSplitMap,
+          SyntheticItems syntheticItems) {
     if (definitions.isEmpty()) {
       return Collections.emptyList();
     }
@@ -817,7 +837,8 @@
     RepresentativeMap map = t -> syntheticTypes.contains(t) ? factory.voidType : t;
     Map<HashCode, List<T>> equivalences = new HashMap<>(definitions.size());
     for (T definition : definitions.values()) {
-      HashCode hash = definition.computeHash(map, intermediate, classToFeatureSplitMap);
+      HashCode hash =
+          definition.computeHash(map, intermediate, classToFeatureSplitMap, syntheticItems);
       equivalences.computeIfAbsent(hash, k -> new ArrayList<>()).add(definition);
     }
     return equivalences.values();
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index 8263f40..9922011 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -84,6 +84,11 @@
 
   private final PendingSynthetics pending = new PendingSynthetics();
 
+  // Empty collection for use only in tests and utilities.
+  public static SyntheticItems empty() {
+    return new SyntheticItems(-1, CommittedSyntheticsCollection.empty());
+  }
+
   // Only for use from initial AppInfo/AppInfoWithClassHierarchy create functions. */
   public static CommittedItems createInitialSyntheticItems(DexApplication application) {
     return new CommittedItems(
@@ -215,6 +220,18 @@
     return isSyntheticClass(clazz.type);
   }
 
+  public Collection<DexType> getSynthesizingContexts(DexType type) {
+    SyntheticReference<?, ?, ?> reference = committed.getNonLegacyItem(type);
+    if (reference != null) {
+      return Collections.singletonList(reference.getContext().getSynthesizingContextType());
+    }
+    SyntheticDefinition<?, ?, ?> definition = pending.nonLegacyDefinitions.get(type);
+    if (definition != null) {
+      return Collections.singletonList(definition.getContext().getSynthesizingContextType());
+    }
+    return Collections.emptyList();
+  }
+
   // TODO(b/180091213): Implement this and remove client provided the oracle.
   public Set<DexReference> getSynthesizingContexts(
       DexProgramClass clazz, SynthesizingContextOracle oracle) {
@@ -445,6 +462,7 @@
 
   Result computeFinalSynthetics(AppView<?> appView) {
     assert !hasPendingSyntheticClasses();
-    return new SyntheticFinalization(appView.options(), committed).computeFinalSynthetics(appView);
+    return new SyntheticFinalization(appView.options(), this, committed)
+        .computeFinalSynthetics(appView);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 21c3447..1534b4a 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -42,6 +42,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.shaking.FilteredClassPath;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.io.ByteStreams;
@@ -611,7 +612,8 @@
                       classDescriptor -> {
                         if (featureSplitConfiguration != null) {
                           DexType type = dexItemFactory.createType(classDescriptor);
-                          FeatureSplit featureSplit = classToFeatureSplitMap.getFeatureSplit(type);
+                          FeatureSplit featureSplit =
+                              classToFeatureSplitMap.getFeatureSplit(type, SyntheticItems.empty());
                           if (featureSplit != null && !featureSplit.isBase()) {
                             return featureSplitArchiveOutputStreams.get(featureSplit);
                           }
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java b/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
index af73519..20c2b51 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
@@ -55,7 +56,6 @@
             r8FullTestBuilder
                 .noMinification()
                 .enableInliningAnnotations()
-                .addInliningAnnotations()
                 .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O));
     ThrowingConsumer<R8TestCompileResult, Exception> ensureLambdaNotInBase =
         r8TestCompileResult -> {
@@ -87,6 +87,10 @@
             .addFeatureSplit(FeatureClass.class)
             .addFeatureSplit(Feature2Class.class)
             .addKeepFeatureMainRules(BaseSuperClass.class, FeatureClass.class, Feature2Class.class)
+            .addKeepMethodRules(
+                Reference.methodFromMethod(
+                    BaseSuperClass.class.getDeclaredMethod(
+                        "keptApplyLambda", MyFunction.class, String.class)))
             .noMinification()
             .enableInliningAnnotations()
             .setMinApi(parameters.getApiLevel())
@@ -108,6 +112,10 @@
     }
 
     public abstract String getFromFeature();
+
+    public String keptApplyLambda(MyFunction fn, String arg) {
+      return fn.apply(arg);
+    }
   }
 
   public interface MyFunction {
@@ -133,7 +141,7 @@
 
     @NeverInline
     private String useTheLambda(MyFunction f) {
-      return f.apply("42");
+      return keptApplyLambda(f, "42");
     }
   }
 
@@ -159,7 +167,7 @@
 
     @NeverInline
     private String useTheLambda(MyFunction f) {
-      return f.apply("43");
+      return keptApplyLambda(f, "43");
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageFeatureWithSyntheticsTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageFeatureWithSyntheticsTest.java
index a203729..4eb6d15 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageFeatureWithSyntheticsTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageFeatureWithSyntheticsTest.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
@@ -37,12 +36,16 @@
   private static final List<Class<?>> FIRST_CLASSES =
       ImmutableList.of(FIRST_FOO, FIRST_PKG_PRIVATE);
 
+  private static final Class<?> FIRST_FIRST_FOO =
+      com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first.first.Foo
+          .class;
+
+  private static final Class<?> FIRST_FIRST_PKG_PRIVATE =
+      com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first.first
+          .PkgProtectedMethod.class;
+
   private static final List<Class<?>> FIRST_FIRST_CLASSES =
-      ImmutableList.of(
-          com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first.first.Foo
-              .class,
-          com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first.first
-              .PkgProtectedMethod.class);
+      ImmutableList.of(FIRST_FIRST_FOO, FIRST_FIRST_PKG_PRIVATE);
 
   private static List<Class<?>> getTestClasses() {
     return ImmutableList.<Class<?>>builder()
@@ -84,11 +87,7 @@
             .addProgramClasses(getTestClasses())
             .addFeatureSplit(getFeatureClasses().toArray(new Class<?>[0]))
             .addKeepMainRule(TestClass.class)
-            .addKeepClassAndMembersRules(
-                com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first
-                    .PkgProtectedMethod.class,
-                com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first
-                    .first.PkgProtectedMethod.class)
+            .addKeepClassAndMembersRules(FIRST_PKG_PRIVATE, FIRST_FIRST_PKG_PRIVATE)
             .addKeepClassAndMembersRules(I.class)
             .addKeepMethodRules(
                 Reference.methodFromMethod(TestClass.class.getDeclaredMethod("run", I.class)))
@@ -106,20 +105,21 @@
     // Check that the first.Foo is repackaged but that the pkg private access class is not.
     // The implies that the lambda which is doing a package private access cannot be repackaged.
     // If it is, the access will fail resulting in a runtime exception.
-    CodeInspector baseInspector = compileResult.inspector();
-    assertThat(FIRST_FOO, isRepackagedAsExpected(baseInspector, "first"));
-    assertThat(FIRST_PKG_PRIVATE, isNotRepackaged(baseInspector));
-    assertEquals(
-        getTestClasses().size() + expectedSyntheticsInBase, baseInspector.allClasses().size());
-
-    // The feature first.first.Foo is not repackaged and neither is the lambda.
-    // TODO(b/180086194): first.first.Foo should have been repackaged, but is currently identified
-    //   as being in 'base' when inlining takes place.
-    CodeInspector featureInspector = new CodeInspector(compileResult.getFeature(0));
-    getFeatureClasses().forEach(c -> assertThat(c, isNotRepackaged(featureInspector)));
-    assertEquals(
-        getFeatureClasses().size() + expectedSyntheticsInFeature,
-        featureInspector.allClasses().size());
+    compileResult.inspect(
+        baseInspector -> {
+          assertThat(FIRST_FOO, isRepackagedAsExpected(baseInspector, "first"));
+          assertThat(FIRST_PKG_PRIVATE, isNotRepackaged(baseInspector));
+          assertEquals(
+              getTestClasses().size() + expectedSyntheticsInBase,
+              baseInspector.allClasses().size());
+        },
+        featureInspector -> {
+          assertThat(FIRST_FIRST_FOO, isRepackagedAsExpected(featureInspector, "first$1"));
+          assertThat(FIRST_FIRST_PKG_PRIVATE, isNotRepackaged(featureInspector));
+          assertEquals(
+              getFeatureClasses().size() + expectedSyntheticsInFeature,
+              featureInspector.allClasses().size());
+        });
 
     compileResult
         .addFeatureSplitsToRunClasspathFiles()