Reland "Use the type as the prefix for external synthetics."

This reverts commit df524d261735db40ca981db5a83bd5b4bb9bd4f6.

Bug: 179174151
Change-Id: Ic327e901f7723249976fef8c31ccb860bc5b4dbb
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index 5ebf3ae..ef99d01 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -36,6 +36,19 @@
   }
 
   @Override
+  public int compareTo(DexReference other) {
+    if (other.isDexField()) {
+      return compareTo(other.asDexField());
+    }
+    if (other.isDexMethod()) {
+      int comparisonResult = getHolderType().compareTo(other.getContextType());
+      return comparisonResult != 0 ? comparisonResult : -1;
+    }
+    int comparisonResult = getHolderType().compareTo(other.asDexType());
+    return comparisonResult != 0 ? comparisonResult : 1;
+  }
+
+  @Override
   public DexField self() {
     return this;
   }
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 29e9792..8484a94 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -36,6 +36,15 @@
   }
 
   @Override
+  public int compareTo(DexReference other) {
+    if (other.isDexMethod()) {
+      return compareTo(other.asDexMethod());
+    }
+    int comparisonResult = getHolderType().compareTo(other.getContextType());
+    return comparisonResult != 0 ? comparisonResult : 1;
+  }
+
+  @Override
   public StructuralMapping<DexMethod> getStructuralMapping() {
     return DexMethod::specify;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexReference.java b/src/main/java/com/android/tools/r8/graph/DexReference.java
index 77976f1..27264bb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexReference.java
+++ b/src/main/java/com/android/tools/r8/graph/DexReference.java
@@ -29,6 +29,8 @@
 
   public abstract void collectIndexedItems(IndexedItemCollection indexedItems);
 
+  public abstract int compareTo(DexReference other);
+
   public abstract DexType getContextType();
 
   public boolean isDexType() {
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 aaeb156..75b592b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -53,6 +53,15 @@
   }
 
   @Override
+  public int compareTo(DexReference other) {
+    if (other.isDexType()) {
+      return compareTo(other.asDexType());
+    }
+    int comparisonResult = compareTo(other.getContextType());
+    return comparisonResult != 0 ? comparisonResult : -1;
+  }
+
+  @Override
   public DexType self() {
     return this;
   }
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 1067e93..82f3204 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -8,15 +8,12 @@
 
 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.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.MainDexInfo;
-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,33 +60,6 @@
     return new SynthesizingContext(synthesizingContextType, clazz.type, clazz.origin);
   }
 
-  static SynthesizingContext fromSyntheticContextChange(
-      SyntheticKind kind,
-      DexType syntheticType,
-      SynthesizingContext oldContext,
-      DexItemFactory factory) {
-    String descriptor = syntheticType.toDescriptorString();
-    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) + ";");
-    }
-    return newContext == oldContext.getSynthesizingContextType()
-        ? oldContext
-        : new SynthesizingContext(newContext, newContext, oldContext.inputContextOrigin);
-  }
-
   private SynthesizingContext(
       DexType synthesizingContextType, DexType inputContextType, Origin inputContextOrigin) {
     this.synthesizingContextType = synthesizingContextType;
@@ -127,10 +97,10 @@
   }
 
   void registerPrefixRewriting(DexType hygienicType, AppView<?> appView) {
-    assert hygienicType.toSourceString().startsWith(synthesizingContextType.toSourceString());
     if (!appView.options().isDesugaredLibraryCompilation()) {
       return;
     }
+    assert hygienicType.toSourceString().startsWith(synthesizingContextType.toSourceString());
     DexType rewrittenContext =
         appView
             .options()
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
index 25142ee..69f87fe 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassReference.java
@@ -30,4 +30,9 @@
   DexType getHolder() {
     return type;
   }
+
+  @Override
+  DexType getReference() {
+    return type;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassReference.java
index 5b9ba3c..72beb13 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClasspathClassReference.java
@@ -34,9 +34,12 @@
   }
 
   @Override
-  SyntheticClasspathClassReference rewrite(NonIdentityGraphLens lens) {
+  SyntheticClasspathClassReference internalRewrite(
+      SynthesizingContext rewrittenContext, NonIdentityGraphLens lens) {
     assert type == lens.lookupType(type)
         : "Unexpected classpath rewrite of type " + type.toSourceString();
+    assert getContext() == rewrittenContext
+        : "Unexpected classpath rewrite of context type " + getContext();
     return this;
   }
 }
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 239e7d0..9520d57 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -59,12 +59,21 @@
     return context;
   }
 
+  final String getPrefixForExternalSyntheticType() {
+    return SyntheticNaming.getPrefixForExternalSyntheticType(getKind(), getHolder().getType());
+  }
+
   public abstract C getHolder();
 
   final HashCode computeHash(
       RepresentativeMap map, boolean intermediate, ClassToFeatureSplitMap classToFeatureSplitMap) {
     Hasher hasher = Hashing.murmur3_128().newHasher();
-    if (intermediate || getKind().isFixedSuffixSynthetic) {
+    if (getKind().isFixedSuffixSynthetic) {
+      // Fixed synthetics are non-shareable. Its unique type is used as the hash key.
+      getHolder().getType().hash(hasher);
+      return hasher.hash();
+    }
+    if (intermediate) {
       // If in intermediate mode, include the context type as sharing is restricted to within a
       // single context.
       getContext().getSynthesizingContextType().hashWithTypeEquivalence(hasher, map);
@@ -95,7 +104,13 @@
       boolean includeContext,
       GraphLens graphLens,
       ClassToFeatureSplitMap classToFeatureSplitMap) {
-    if (includeContext || getKind().isFixedSuffixSynthetic) {
+    DexType thisType = getHolder().getType();
+    DexType otherType = other.getHolder().getType();
+    if (getKind().isFixedSuffixSynthetic) {
+      // Fixed synthetics are non-shareable. Ordered by their unique type.
+      return thisType.compareTo(otherType);
+    }
+    if (includeContext) {
       int order = getContext().compareTo(other.getContext());
       if (order != 0) {
         return order;
@@ -113,8 +128,6 @@
         return order;
       }
     }
-    DexType thisType = getHolder().getType();
-    DexType otherType = other.getHolder().getType();
     RepresentativeMap map = null;
     // If the synthetics have been moved include the original types in the equivalence.
     if (graphLens.isNonIdentityLens()) {
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 741831e..60dca8f 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -265,7 +265,7 @@
         ImmutableMap.builder();
     List<DexProgramClass> finalSyntheticProgramDefinitions = new ArrayList<>();
     {
-      Map<DexType, NumberGenerator> generators = new IdentityHashMap<>();
+      Map<String, NumberGenerator> generators = new HashMap<>();
       application =
           buildLensAndProgram(
               appView,
@@ -317,7 +317,7 @@
       Map<DexType, EquivalenceGroup<D>> computeEquivalences(
           AppView<?> appView,
           ImmutableCollection<R> references,
-          Map<DexType, NumberGenerator> generators) {
+          Map<String, NumberGenerator> generators) {
     boolean intermediate = appView.options().intermediate;
     Map<DexType, D> definitions = lookupDefinitions(appView, references);
     ClassToFeatureSplitMap classToFeatureSplitMap =
@@ -662,11 +662,11 @@
   private <T extends SyntheticDefinition<?, T, ?>>
       Map<DexType, EquivalenceGroup<T>> computeActualEquivalences(
           Collection<List<T>> potentialEquivalences,
-          Map<DexType, NumberGenerator> generators,
+          Map<String, NumberGenerator> generators,
           AppView<?> appView,
           boolean intermediate,
           ClassToFeatureSplitMap classToFeatureSplitMap) {
-    Map<DexType, List<EquivalenceGroup<T>>> groupsPerContext = new IdentityHashMap<>();
+    Map<String, List<EquivalenceGroup<T>>> groupsPerPrefix = new HashMap<>();
     potentialEquivalences.forEach(
         members -> {
           List<List<T>> groups =
@@ -677,17 +677,16 @@
             // The representative is required to be the first element of the group.
             group.remove(representative);
             group.add(0, representative);
-            groupsPerContext
+            groupsPerPrefix
                 .computeIfAbsent(
-                    representative.getContext().getSynthesizingContextType(),
-                    k -> new ArrayList<>())
+                    representative.getPrefixForExternalSyntheticType(), k -> new ArrayList<>())
                 .add(new EquivalenceGroup<>(representative, group));
           }
         });
 
     Map<DexType, EquivalenceGroup<T>> equivalences = new IdentityHashMap<>();
-    groupsPerContext.forEach(
-        (context, groups) -> {
+    groupsPerPrefix.forEach(
+        (externalSyntheticTypePrefix, groups) -> {
           // Sort the equivalence groups that go into 'context' including the context type of the
           // representative which is equal to 'context' here (see assert below).
           groups.sort(
@@ -695,14 +694,18 @@
                   a.compareToIncludingContext(b, appView.graphLens(), classToFeatureSplitMap));
           for (int i = 0; i < groups.size(); i++) {
             EquivalenceGroup<T> group = groups.get(i);
-            assert group.getRepresentative().getContext().getSynthesizingContextType() == context;
+            assert group
+                .getRepresentative()
+                .getPrefixForExternalSyntheticType()
+                .equals(externalSyntheticTypePrefix);
             // Two equivalence groups in same context type must be distinct otherwise the assignment
             // of the synthetic name will be non-deterministic between the two.
             assert i == 0
                 || checkGroupsAreDistinct(
                     groups.get(i - 1), group, appView.graphLens(), classToFeatureSplitMap);
             SyntheticKind kind = group.members.get(0).getKind();
-            DexType representativeType = createExternalType(kind, context, generators, appView);
+            DexType representativeType =
+                createExternalType(kind, externalSyntheticTypePrefix, generators, appView);
             equivalences.put(representativeType, group);
           }
         });
@@ -752,7 +755,7 @@
     T smallest = members.get(0);
     for (int i = 1; i < members.size(); i++) {
       T next = members.get(i);
-      if (next.compareTo(smallest, true, graphLens, classToFeatureSplitMap) < 0) {
+      if (next.toReference().getReference().compareTo(smallest.toReference().getReference()) < 0) {
         smallest = next;
       }
     }
@@ -761,20 +764,20 @@
 
   private DexType createExternalType(
       SyntheticKind kind,
-      DexType representativeContext,
-      Map<DexType, NumberGenerator> generators,
+      String externalSyntheticTypePrefix,
+      Map<String, NumberGenerator> generators,
       AppView<?> appView) {
     DexItemFactory factory = appView.dexItemFactory();
     if (kind.isFixedSuffixSynthetic) {
-      return SyntheticNaming.createExternalType(kind, representativeContext, "", factory);
+      return SyntheticNaming.createExternalType(kind, externalSyntheticTypePrefix, "", factory);
     }
     NumberGenerator generator =
-        generators.computeIfAbsent(representativeContext, k -> new NumberGenerator());
+        generators.computeIfAbsent(externalSyntheticTypePrefix, k -> new NumberGenerator());
     DexType externalType;
     do {
       externalType =
           SyntheticNaming.createExternalType(
-              kind, representativeContext, Integer.toString(generator.next()), factory);
+              kind, externalSyntheticTypePrefix, 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/SyntheticMethodReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
index 930443a..fd63ea2 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodReference.java
@@ -34,6 +34,11 @@
   }
 
   @Override
+  DexMethod getReference() {
+    return method;
+  }
+
+  @Override
   SyntheticMethodDefinition lookupDefinition(Function<DexType, DexClass> definitions) {
     DexClass clazz = definitions.apply(method.holder);
     if (clazz == null) {
@@ -47,7 +52,8 @@
   }
 
   @Override
-  SyntheticMethodReference rewrite(NonIdentityGraphLens lens) {
+  SyntheticMethodReference internalRewrite(
+      SynthesizingContext rewrittenContext, NonIdentityGraphLens lens) {
     DexMethod rewritten = lens.lookupMethod(method);
     // If the reference has been non-trivially rewritten the compiler has changed it and it can no
     // longer be considered a synthetic. The context may or may not have changed.
@@ -58,20 +64,10 @@
       assert SyntheticNaming.verifyNotInternalSynthetic(rewritten.holder);
       return null;
     }
-    SynthesizingContext context = getContext().rewrite(lens);
-    if (context == getContext() && rewritten == method) {
+    if (rewrittenContext == getContext() && rewritten == method) {
       return this;
     }
-    // Ensure that if a synthetic moves its context moves consistently.
-    if (method != rewritten) {
-      context =
-          SynthesizingContext.fromSyntheticContextChange(
-              getKind(), rewritten.holder, context, lens.dexItemFactory());
-      if (context == null) {
-        return null;
-      }
-    }
-    return new SyntheticMethodReference(getKind(), context, rewritten);
+    return new SyntheticMethodReference(getKind(), rewrittenContext, rewritten);
   }
 
   @Override
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 63772a2..25e59ae 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.synthesis;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.references.ClassReference;
@@ -62,24 +63,32 @@
     }
   }
 
+  private static final String SYNTHETIC_CLASS_SEPARATOR = "-$$";
   /**
    * The internal synthetic class separator is only used for representing synthetic items during
    * compilation. In particular, this separator must never be used to write synthetic classes to the
    * final compilation result.
    */
-  private static final String INTERNAL_SYNTHETIC_CLASS_SEPARATOR = "-$$InternalSynthetic";
+  private static final String INTERNAL_SYNTHETIC_CLASS_SEPARATOR =
+      SYNTHETIC_CLASS_SEPARATOR + "InternalSynthetic";
   /**
    * The external synthetic class separator is used when writing classes. It may appear in types
    * during compilation as the output of a compilation may be the input to another.
    */
-  private static final String EXTERNAL_SYNTHETIC_CLASS_SEPARATOR = "-$$ExternalSynthetic";
+  private static final String EXTERNAL_SYNTHETIC_CLASS_SEPARATOR =
+      SYNTHETIC_CLASS_SEPARATOR + "ExternalSynthetic";
   /** Method prefix when generating synthetic methods in a class. */
   static final String INTERNAL_SYNTHETIC_METHOD_PREFIX = "m";
 
-  // TODO(b/158159959): Remove usage of name-based identification.
-  public static boolean isSyntheticName(String typeName) {
-    return typeName.contains(INTERNAL_SYNTHETIC_CLASS_SEPARATOR)
-        || typeName.contains(EXTERNAL_SYNTHETIC_CLASS_SEPARATOR);
+  static String getPrefixForExternalSyntheticType(SyntheticKind kind, DexType type) {
+    String binaryName = type.toBinaryName();
+    int index =
+        binaryName.lastIndexOf(
+            kind.isFixedSuffixSynthetic ? kind.descriptor : SYNTHETIC_CLASS_SEPARATOR);
+    if (index < 0) {
+      throw new Unreachable("Unexpected failure to compute an synthetic prefix");
+    }
+    return binaryName.substring(0, index);
   }
 
   public static DexType createFixedType(
@@ -100,12 +109,12 @@
   }
 
   static DexType createExternalType(
-      SyntheticKind kind, DexType context, String id, DexItemFactory factory) {
+      SyntheticKind kind, String externalSyntheticTypePrefix, String id, DexItemFactory factory) {
     assert kind.isFixedSuffixSynthetic == id.isEmpty();
     return createType(
         kind.isFixedSuffixSynthetic ? "" : EXTERNAL_SYNTHETIC_CLASS_SEPARATOR,
         kind,
-        context,
+        externalSyntheticTypePrefix,
         id,
         factory);
   }
@@ -115,10 +124,19 @@
     return factory.createType(createDescriptor(separator, kind, context.getInternalName(), id));
   }
 
+  private static DexType createType(
+      String separator,
+      SyntheticKind kind,
+      String externalSyntheticTypePrefix,
+      String id,
+      DexItemFactory factory) {
+    return factory.createType(createDescriptor(separator, kind, externalSyntheticTypePrefix, id));
+  }
+
   private static String createDescriptor(
-      String separator, SyntheticKind kind, String context, String id) {
+      String separator, SyntheticKind kind, String externalSyntheticTypePrefix, String id) {
     return DescriptorUtils.getDescriptorFromClassBinaryName(
-        context + separator + kind.descriptor + id);
+        externalSyntheticTypePrefix + separator + kind.descriptor + id);
   }
 
   public static boolean verifyNotInternalSynthetic(DexType type) {
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 7e798e0..5a17b04 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticProgramClassReference.java
@@ -36,7 +36,8 @@
   }
 
   @Override
-  SyntheticProgramClassReference rewrite(NonIdentityGraphLens lens) {
+  SyntheticProgramClassReference internalRewrite(
+      SynthesizingContext rewrittenContext, NonIdentityGraphLens lens) {
     DexType rewritten = lens.lookupType(type);
     // If the reference has been non-trivially rewritten the compiler has changed it and it can no
     // longer be considered a synthetic. The context may or may not have changed.
@@ -46,20 +47,10 @@
       assert SyntheticNaming.verifyNotInternalSynthetic(rewritten);
       return null;
     }
-    SynthesizingContext context = getContext().rewrite(lens);
-    if (context == getContext() && rewritten == type) {
+    if (rewrittenContext == getContext() && rewritten == type) {
       return this;
     }
-    // Ensure that if a synthetic moves its context moves consistently.
-    if (type != rewritten) {
-      context =
-          SynthesizingContext.fromSyntheticContextChange(
-              getKind(), rewritten, context, lens.dexItemFactory());
-      if (context == null) {
-        return null;
-      }
-    }
-    return new SyntheticProgramClassReference(getKind(), context, rewritten);
+    return new SyntheticProgramClassReference(getKind(), rewrittenContext, rewritten);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
index 1a2d2fb..ddb28f7 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticReference.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.synthesis;
 
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
@@ -20,13 +21,13 @@
     C extends DexClass> {
 
   private final SyntheticKind kind;
-  private final SynthesizingContext context;
+  private final SynthesizingContext rewrittenContext;
 
   SyntheticReference(SyntheticKind kind, SynthesizingContext context) {
     assert kind != null;
     assert context != null;
     this.kind = kind;
-    this.context = context;
+    this.rewrittenContext = context;
   }
 
   abstract D lookupDefinition(Function<DexType, DexClass> definitions);
@@ -36,10 +37,17 @@
   }
 
   final SynthesizingContext getContext() {
-    return context;
+    return rewrittenContext;
   }
 
   abstract DexType getHolder();
 
-  abstract R rewrite(NonIdentityGraphLens lens);
+  abstract DexReference getReference();
+
+  final R rewrite(NonIdentityGraphLens lens) {
+    SynthesizingContext rewrittenContext = getContext().rewrite(lens);
+    return internalRewrite(rewrittenContext, lens);
+  }
+
+  abstract R internalRewrite(SynthesizingContext rewrittenContext, NonIdentityGraphLens lens);
 }