Hygienic nest desugaring of private constructor access.

Bug: 158159959
Change-Id: I5c9e63bc1e4fecd21fcecbbeee011e45abf6d0d7
Fixes: 176900254
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 30ac84b..9675f9c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -25,7 +25,6 @@
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -454,10 +453,6 @@
       createStaticallyKnownType("Landroid/util/Property;");
   public final DexType androidViewViewType = createStaticallyKnownType("Landroid/view/View;");
 
-  public final DexString nestConstructorDescriptor =
-      createString("L" + NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME + ";");
-  public final DexType nestConstructorType = createStaticallyKnownType(nestConstructorDescriptor);
-
   public final StringBuildingMethods stringBuilderMethods =
       new StringBuildingMethods(stringBuilderType);
   public final StringBuildingMethods stringBufferMethods =
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 76d2ea4..902e3b7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -8,7 +8,6 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.structural.CompareToVisitor;
@@ -36,6 +35,7 @@
           "$-DC",
           "$$ServiceLoaderMethods",
           "com.android.tools.r8.GeneratedOutlineSupport",
+          "-$$Nest$Constructor",
           "-$$Lambda$");
 
   public final DexString descriptor;
@@ -308,8 +308,7 @@
   private static boolean isSynthesizedTypeThatCouldBeDuplicated(String name) {
     // Any entry that is removed from here must be added to OLD_SYNTHESIZED_NAMES to ensure that
     // newer releases can be used to merge previous builds.
-    return name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX) // Could collide.
-        || name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME); // Global singleton.
+    return name.contains(LAMBDA_GROUP_CLASS_NAME_PREFIX);
   }
 
   private boolean oldSynthesizedName(String name) {
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 4131111..6960b96 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
@@ -398,12 +398,8 @@
     }
   }
 
-  private void finalizeNestBasedAccessDesugaring(Builder<?> builder) {
+  private void reportNestDesugarDependencies() {
     if (d8NestBasedAccessDesugaring != null) {
-      DexProgramClass nestConstructor = d8NestBasedAccessDesugaring.synthesizeNestConstructor();
-      if (nestConstructor != null) {
-        builder.addProgramClass(nestConstructor);
-      }
       d8NestBasedAccessDesugaring.reportDesugarDependencies();
     }
   }
@@ -489,10 +485,7 @@
 
     convertClasses(application, executor);
 
-    // Build a new application with jumbo string info,
-    Builder<?> builder = application.builder().setHighestSortingString(highestSortingString);
-
-    finalizeNestBasedAccessDesugaring(builder);
+    reportNestDesugarDependencies();
 
     // Synthesize lambda classes and commit to the app in full.
     synthesizeLambdaClasses(executor);
@@ -500,13 +493,14 @@
     if (appView.getSyntheticItems().hasPendingSyntheticClasses()) {
       appView.setAppInfo(
           new AppInfo(
-              appView.appInfo().getSyntheticItems().commit(builder.build()),
+              appView.appInfo().getSyntheticItems().commit(application),
               appView.appInfo().getMainDexClasses()));
       application = appView.appInfo().app();
-      builder = application.builder();
-      builder.setHighestSortingString(highestSortingString);
     }
 
+    // Build a new application with jumbo string info,
+    Builder<?> builder = application.builder().setHighestSortingString(highestSortingString);
+
     desugarInterfaceMethods(builder, ExcludeDexResources, executor);
     processSynthesizedJava8UtilityClasses(executor);
     synthesizeRetargetClass(builder, executor);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
index cbaa05a..c516ce8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
@@ -10,18 +10,14 @@
 import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndField;
 import com.android.tools.r8.graph.DexClassAndMember;
 import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMember;
@@ -30,21 +26,20 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.LibraryMember;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
-import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 import org.objectweb.asm.Opcodes;
 
@@ -63,14 +58,10 @@
   private static final String NEST_ACCESS_FIELD_PUT_NAME_PREFIX = NEST_ACCESS_NAME_PREFIX + "fput";
   private static final String NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX =
       NEST_ACCESS_NAME_PREFIX + "sfput";
-  public static final String NEST_CONSTRUCTOR_NAME = NEST_ACCESS_NAME_PREFIX + "Constructor";
 
   protected final AppView<?> appView;
   private final DexItemFactory dexItemFactory;
-
-  // Common single empty class for nest based private constructors
-  private DexProgramClass nestConstructor;
-  private boolean nestConstructorUsed;
+  private final Map<DexType, DexType> syntheticNestConstructorTypes = new ConcurrentHashMap<>();
 
   public NestBasedAccessDesugaring(AppView<?> appView) {
     this.appView = appView;
@@ -234,40 +225,6 @@
     throw appView.options().errorMissingNestMember(nest);
   }
 
-  private DexProgramClass createNestAccessConstructor() {
-    // TODO(b/176900254): ensure hygienic synthetic class.
-    return new DexProgramClass(
-        dexItemFactory.nestConstructorType,
-        null,
-        new SynthesizedOrigin("Nest based access desugaring", getClass()),
-        // Make the synthesized class public since shared in the whole program.
-        ClassAccessFlags.fromDexAccessFlags(
-            Constants.ACC_FINAL | Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC),
-        dexItemFactory.objectType,
-        DexTypeList.empty(),
-        dexItemFactory.createString("nest"),
-        null,
-        Collections.emptyList(),
-        null,
-        Collections.emptyList(),
-        ClassSignature.noSignature(),
-        DexAnnotationSet.empty(),
-        DexEncodedField.EMPTY_ARRAY,
-        DexEncodedField.EMPTY_ARRAY,
-        DexEncodedMethod.EMPTY_ARRAY,
-        DexEncodedMethod.EMPTY_ARRAY,
-        dexItemFactory.getSkipNameValidationForTesting(),
-        DexProgramClass::checksumFromType);
-  }
-
-  public DexProgramClass synthesizeNestConstructor() {
-    if (nestConstructorUsed && nestConstructor == null) {
-      nestConstructor = createNestAccessConstructor();
-      return nestConstructor;
-    }
-    return null;
-  }
-
   DexMethod ensureFieldAccessBridge(
       DexClassAndField field, boolean isGet, NestBridgeConsumer bridgeConsumer) {
     if (field.isProgramField()) {
@@ -360,9 +317,31 @@
 
   private DexMethod getMethodBridgeReference(DexClassAndMethod method) {
     if (method.getDefinition().isInstanceInitializer()) {
-      DexProto newProto =
-          dexItemFactory.appendTypeToProto(method.getProto(), dexItemFactory.nestConstructorType);
-      nestConstructorUsed = true;
+      DexType nestConstructorType =
+          syntheticNestConstructorTypes.computeIfAbsent(
+              method.getHolderType(),
+              holder -> {
+                if (method.isProgramMethod()) {
+                  return appView
+                      .getSyntheticItems()
+                      .createFixedClass(
+                          SyntheticKind.INIT_TYPE_ARGUMENT,
+                          method.asProgramMethod().getHolder(),
+                          dexItemFactory,
+                          builder -> {})
+                      .getType();
+                } else {
+                  assert method.isClasspathMethod();
+                  return appView
+                      .getSyntheticItems()
+                      .createFixedClasspathClass(
+                          SyntheticKind.INIT_TYPE_ARGUMENT,
+                          method.asClasspathMethod().getHolder(),
+                          dexItemFactory)
+                      .getType();
+                }
+              });
+      DexProto newProto = dexItemFactory.appendTypeToProto(method.getProto(), nestConstructorType);
       return method.getReference().withProto(newProto, dexItemFactory);
     }
     DexProto proto =
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index ce1dadb..8d661f9 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3155,8 +3155,6 @@
 
     Map<DexMethod, ProgramMethod> liveMethods = new IdentityHashMap<>();
 
-    Map<DexType, DexProgramClass> syntheticProgramClasses = new IdentityHashMap<>();
-
     Map<DexType, DexClasspathClass> syntheticClasspathClasses = new IdentityHashMap<>();
 
     // Subset of live methods that need have keep requirements.
@@ -3171,7 +3169,6 @@
           desugaredMethods.isEmpty()
               && syntheticInstantiations.isEmpty()
               && liveMethods.isEmpty()
-              && syntheticProgramClasses.isEmpty()
               && syntheticClasspathClasses.isEmpty();
       assert !empty || (liveMethodsWithKeepActions.isEmpty() && mainDexTypes.isEmpty());
       return empty;
@@ -3187,11 +3184,6 @@
       assert old == null;
     }
 
-    void addProgramClass(DexProgramClass clazz) {
-      DexProgramClass old = syntheticProgramClasses.put(clazz.type, clazz);
-      assert old == null;
-    }
-
     void addLiveMethod(ProgramMethod method) {
       DexMethod signature = method.getDefinition().method;
       assert !liveMethods.containsKey(signature);
@@ -3206,7 +3198,6 @@
 
     void amendApplication(Builder appBuilder) {
       assert !isEmpty();
-      appBuilder.addProgramClasses(syntheticProgramClasses.values());
       appBuilder.addClasspathClasses(syntheticClasspathClasses.values());
     }
 
@@ -3283,7 +3274,6 @@
     synthesizeLambdas(additions);
     synthesizeLibraryConversionWrappers(additions);
     synthesizeBackports(additions);
-    synthesizeNestConstructor(additions);
     synthesizeTwrCloseResource(additions);
     if (additions.isEmpty()) {
       return;
@@ -3372,16 +3362,6 @@
     }
   }
 
-  private void synthesizeNestConstructor(SyntheticAdditions additions) {
-    if (nestBasedAccessRewriter != null) {
-      DexProgramClass nestConstructor = nestBasedAccessRewriter.synthesizeNestConstructor();
-      if (nestConstructor != null) {
-        // TODO(b/177638147): use getSyntheticItems().createClass().
-        additions.addProgramClass(nestConstructor);
-      }
-    }
-  }
-
   private void synthesizeTwrCloseResource(SyntheticAdditions additions) {
     for (ProgramMethod method : methodsWithTwrCloseResource.values()) {
       twrCloseResourceRewriter.rewriteCf(method, additions::addLiveMethod);
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
index 932359d..f6da35b 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.synthesis.SyntheticNaming.Phase;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import java.util.Comparator;
 import java.util.Set;
 
@@ -63,14 +64,27 @@
   }
 
   static SynthesizingContext fromSyntheticContextChange(
-      DexType syntheticType, SynthesizingContext oldContext, DexItemFactory factory) {
+      SyntheticKind kind,
+      DexType syntheticType,
+      SynthesizingContext oldContext,
+      DexItemFactory factory) {
     String descriptor = syntheticType.toDescriptorString();
-    int i = descriptor.indexOf(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
-    if (i <= 0) {
-      assert false : "Unexpected synthetic without internal separator: " + syntheticType;
-      return null;
+    DexType newContext;
+    if (kind.isFixedSuffixSynthetic) {
+      int i = descriptor.lastIndexOf(kind.descriptor);
+      if (i < 0 || descriptor.length() != i + kind.descriptor.length() + 1) {
+        assert false : "Unexpected fixed synthetic with invalid suffix: " + syntheticType;
+        return null;
+      }
+      newContext = factory.createType(descriptor.substring(0, i) + ";");
+    } else {
+      int i = descriptor.indexOf(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
+      if (i <= 0) {
+        assert false : "Unexpected synthetic without internal separator: " + syntheticType;
+        return null;
+      }
+      newContext = factory.createType(descriptor.substring(0, i) + ";");
     }
-    DexType newContext = factory.createType(descriptor.substring(0, i) + ";");
     return newContext == oldContext.getSynthesizingContextType()
         ? oldContext
         : new SynthesizingContext(newContext, newContext, oldContext.inputContextOrigin);
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 eefe1b9..239e7d0 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -64,7 +64,7 @@
   final HashCode computeHash(
       RepresentativeMap map, boolean intermediate, ClassToFeatureSplitMap classToFeatureSplitMap) {
     Hasher hasher = Hashing.murmur3_128().newHasher();
-    if (intermediate) {
+    if (intermediate || getKind().isFixedSuffixSynthetic) {
       // If in intermediate mode, include the context type as sharing is restricted to within a
       // single context.
       getContext().getSynthesizingContextType().hashWithTypeEquivalence(hasher, map);
@@ -95,7 +95,7 @@
       boolean includeContext,
       GraphLens graphLens,
       ClassToFeatureSplitMap classToFeatureSplitMap) {
-    if (includeContext) {
+    if (includeContext || getKind().isFixedSuffixSynthetic) {
       int order = getContext().compareTo(other.getContext());
       if (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 e2e0b55..2d5ec60 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -765,16 +765,17 @@
       DexType representativeContext,
       Map<DexType, NumberGenerator> generators,
       AppView<?> appView) {
+    DexItemFactory factory = appView.dexItemFactory();
+    if (kind.isFixedSuffixSynthetic) {
+      return SyntheticNaming.createExternalType(kind, representativeContext, "", factory);
+    }
     NumberGenerator generator =
         generators.computeIfAbsent(representativeContext, k -> new NumberGenerator());
     DexType externalType;
     do {
       externalType =
           SyntheticNaming.createExternalType(
-              kind,
-              representativeContext,
-              Integer.toString(generator.next()),
-              appView.dexItemFactory());
+              kind, representativeContext, Integer.toString(generator.next()), factory);
       DexClass clazz = appView.appInfo().definitionForWithoutExistenceAssert(externalType);
       if (clazz != null && isNotSyntheticType(clazz.type)) {
         assert options.testing.allowConflictingSyntheticTypes
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 c198197..c40dbee 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -305,6 +305,36 @@
     return clazz;
   }
 
+  public DexProgramClass createFixedClass(
+      SyntheticKind kind,
+      DexProgramClass context,
+      DexItemFactory factory,
+      Consumer<SyntheticProgramClassBuilder> fn) {
+    // Obtain the outer synthesizing context in the case the context itself is synthetic.
+    // This is to ensure a flat input-type -> synthetic-item mapping.
+    SynthesizingContext outerContext = getSynthesizingContext(context);
+    DexType type = SyntheticNaming.createFixedType(kind, outerContext, factory);
+    SyntheticProgramClassBuilder classBuilder =
+        new SyntheticProgramClassBuilder(type, outerContext, factory);
+    fn.accept(classBuilder);
+    DexProgramClass clazz = classBuilder.build();
+    addPendingDefinition(new SyntheticProgramClassDefinition(kind, outerContext, clazz));
+    return clazz;
+  }
+
+  public DexClasspathClass createFixedClasspathClass(
+      SyntheticKind kind, DexClasspathClass context, DexItemFactory factory) {
+    // Obtain the outer synthesizing context in the case the context itself is synthetic.
+    // This is to ensure a flat input-type -> synthetic-item mapping.
+    SynthesizingContext outerContext = SynthesizingContext.fromNonSyntheticInputContext(context);
+    DexType type = SyntheticNaming.createFixedType(kind, outerContext, factory);
+    SyntheticClasspathClassBuilder classBuilder =
+        new SyntheticClasspathClassBuilder(type, outerContext, factory);
+    DexClasspathClass clazz = classBuilder.build();
+    addPendingDefinition(new SyntheticClasspathClassDefinition(kind, outerContext, clazz));
+    return clazz;
+  }
+
   /** Create a single synthetic method item. */
   public ProgramMethod createMethod(
       SyntheticKind kind,
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
index 09a0f1a..0b53962 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -66,7 +66,7 @@
     if (method != rewritten) {
       context =
           SynthesizingContext.fromSyntheticContextChange(
-              rewritten.holder, context, lens.dexItemFactory());
+              getKind(), rewritten.holder, context, lens.dexItemFactory());
       if (context == null) {
         return null;
       }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index fc5b278..c433ec0 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -24,6 +24,7 @@
     // Class synthetics.
     COMPANION_CLASS("CompanionClass", false),
     LAMBDA("Lambda", false),
+    INIT_TYPE_ARGUMENT("-IA", false, true),
     // Method synthetics.
     BACKPORT("Backport", true),
     STATIC_INTERFACE_CALL("StaticInterfaceCall", true),
@@ -37,10 +38,17 @@
 
     public final String descriptor;
     public final boolean isSingleSyntheticMethod;
+    public final boolean isFixedSuffixSynthetic;
 
     SyntheticKind(String descriptor, boolean isSingleSyntheticMethod) {
+      this(descriptor, isSingleSyntheticMethod, false);
+    }
+
+    SyntheticKind(
+        String descriptor, boolean isSingleSyntheticMethod, boolean isFixedSuffixSynthetic) {
       this.descriptor = descriptor;
       this.isSingleSyntheticMethod = isSingleSyntheticMethod;
+      this.isFixedSuffixSynthetic = isFixedSuffixSynthetic;
     }
 
     public static SyntheticKind fromDescriptor(String descriptor) {
@@ -73,8 +81,15 @@
         || typeName.contains(EXTERNAL_SYNTHETIC_CLASS_SEPARATOR);
   }
 
+  public static DexType createFixedType(
+      SyntheticKind kind, SynthesizingContext context, DexItemFactory factory) {
+    assert kind.isFixedSuffixSynthetic;
+    return createType("", kind, context.getSynthesizingContextType(), "", factory);
+  }
+
   static DexType createInternalType(
       SyntheticKind kind, SynthesizingContext context, String id, DexItemFactory factory) {
+    assert !kind.isFixedSuffixSynthetic;
     return createType(
         INTERNAL_SYNTHETIC_CLASS_SEPARATOR,
         kind,
@@ -85,7 +100,13 @@
 
   static DexType createExternalType(
       SyntheticKind kind, DexType context, String id, DexItemFactory factory) {
-    return createType(EXTERNAL_SYNTHETIC_CLASS_SEPARATOR, kind, context, id, factory);
+    assert kind.isFixedSuffixSynthetic == id.isEmpty();
+    return createType(
+        kind.isFixedSuffixSynthetic ? "" : EXTERNAL_SYNTHETIC_CLASS_SEPARATOR,
+        kind,
+        context,
+        id,
+        factory);
   }
 
   private static DexType createType(
@@ -134,6 +155,10 @@
 
   static boolean isSynthetic(ClassReference clazz, Phase phase, SyntheticKind kind) {
     String typeName = clazz.getTypeName();
+    if (kind.isFixedSuffixSynthetic) {
+      assert phase == null;
+      return clazz.getBinaryName().endsWith(kind.descriptor);
+    }
     String separator = getPhaseSeparator(phase);
     int i = typeName.indexOf(separator);
     return i >= 0 && checkMatchFrom(kind, typeName, i, separator, phase == Phase.EXTERNAL);
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassDefinition.java
index 8b42eb6..3506dab 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassDefinition.java
@@ -48,7 +48,9 @@
 
   @Override
   public boolean isValid() {
-    return clazz.isPublic() && clazz.isFinal() && clazz.accessFlags.isSynthetic();
+    return clazz.isPublic()
+        && clazz.accessFlags.isSynthetic()
+        && (clazz.isFinal() || clazz.isAbstract());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
index 3472268..7e798e0 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
@@ -53,7 +53,8 @@
     // Ensure that if a synthetic moves its context moves consistently.
     if (type != rewritten) {
       context =
-          SynthesizingContext.fromSyntheticContextChange(rewritten, context, lens.dexItemFactory());
+          SynthesizingContext.fromSyntheticContextChange(
+              getKind(), rewritten, context, lens.dexItemFactory());
       if (context == null) {
         return null;
       }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index 8a9fc12..4de3828 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -24,7 +24,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser;
-import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.tracereferences.TraceReferences;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -154,15 +153,8 @@
                             .startsWith(
                                 "Invalid parameter counts in MethodParameter attributes.")));
       }
-      // TODO(b/176900254): The nest check should not be necessary.
       new CodeInspector(desugaredLib, mapping)
-          .forAllClasses(
-              clazz ->
-                  assertTrue(
-                      clazz.getFinalName().startsWith("j$.")
-                          || clazz
-                              .getOriginalName()
-                              .startsWith(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME)));
+          .forAllClasses(clazz -> assertTrue(clazz.getFinalName().startsWith("j$.")));
       return desugaredLib;
     } catch (Exception e) {
       // Don't wrap assumption violation so junit can catch it.
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java
index c55df8c..6557f52 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/MinimumNumberOfBridgesGenerated.java
@@ -13,7 +13,10 @@
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
@@ -79,9 +82,14 @@
   private boolean isNestBridge(FoundMethodSubject methodSubject) {
     DexEncodedMethod method = methodSubject.getMethod();
     if (method.isInstanceInitializer()) {
-      return method.method.proto.parameters.size() > 0 && method.method.proto.parameters.values[
-          method.method.proto.parameters.size() - 1].toSourceString()
-          .contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME);
+      if (method.method.proto.parameters.isEmpty()) {
+        return false;
+      }
+      DexType[] formals = method.method.proto.parameters.values;
+      DexType lastFormal = formals[formals.length - 1];
+      return lastFormal.isClassType()
+          && SyntheticItemsTestUtils.isInitializerTypeArgument(
+              Reference.classFromDescriptor(lastFormal.toDescriptorString()));
     }
     return method.method.name.toString()
         .startsWith(NestBasedAccessDesugaring.NEST_ACCESS_NAME_PREFIX);
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index 3f9c6af..6f3a6cc 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -86,6 +86,10 @@
     return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.OUTLINE);
   }
 
+  public static boolean isInitializerTypeArgument(ClassReference reference) {
+    return SyntheticNaming.isSynthetic(reference, null, SyntheticKind.INIT_TYPE_ARGUMENT);
+  }
+
   public static Matcher<String> containsInternalSyntheticReference() {
     return containsString(SyntheticNaming.getPhaseSeparator(Phase.INTERNAL));
   }