Modify Position to use sub types for synthetics

Change-Id: I787d2a28182e1f79eaf08d8e8924706d396ff8d3
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d68ff6b..dcc55b1 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -994,7 +994,7 @@
         continue;
       }
       CfPosition position = instruction.asPosition();
-      assert position.getPosition().getOutermostCaller().method == originalMethod;
+      assert position.getPosition().getOutermostCaller().getMethod() == originalMethod;
     }
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 98808c7..3bdfcbf 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -555,8 +555,8 @@
   public void print(CfPosition instruction) {
     Position position = instruction.getPosition();
     indent();
-    builder.append(".line ").append(position.line);
-    if (position.file != null || position.callerPosition != null) {
+    builder.append(".line ").append(position.getLine());
+    if (position.hasCallerPosition() || position.hasFile()) {
       appendComment(position.toString());
     }
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
index 45077e2..327bb51 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfPosition.java
@@ -47,7 +47,7 @@
         this,
         (CfPosition) other,
         spec ->
-            spec.withInt(p -> p.position.line)
+            spec.withInt(p -> p.position.getLine())
                 .withCustomItem(p -> p.label, helper.labelAcceptor()));
   }
 
@@ -61,7 +61,7 @@
       NamingLens namingLens,
       LensCodeRewriterUtils rewriter,
       MethodVisitor visitor) {
-    visitor.visitLineNumber(position.line, label.getLabel());
+    visitor.visitLineNumber(position.getLine(), label.getLabel());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index c043d4a..4589a49 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
@@ -763,7 +764,7 @@
 
   @Override
   public Code getCodeAsInlining(DexMethod caller, DexMethod callee) {
-    Position callerPosition = Position.synthetic(0, caller, null);
+    Position callerPosition = SyntheticPosition.builder().setLine(0).setMethod(caller).build();
     List<CfInstruction> newInstructions = new ArrayList<>(instructions.size() + 2);
     CfLabel firstLabel;
     if (instructions.get(0).isLabel()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 8409ee1..3dd956f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.conversion.DexSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
@@ -180,7 +181,7 @@
   }
 
   private DexDebugInfo debugInfoAsInlining(DexMethod caller, DexMethod callee) {
-    Position callerPosition = Position.synthetic(0, caller, null);
+    Position callerPosition = SyntheticPosition.builder().setLine(0).setMethod(caller).build();
     if (debugInfo == null) {
       // If the method has no debug info we generate a preamble position to denote the inlining.
       // This is consistent with the building IR for inlining which will always ensure the method
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
index 8eb786a..2547996 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
@@ -63,8 +63,8 @@
       builder.append(":").append(method.name);
       Position caller = callerPosition;
       while (caller != null) {
-        builder.append(";").append(caller.line).append(":").append(caller.method.name);
-        caller = caller.callerPosition;
+        builder.append(";").append(caller.getLine()).append(":").append(caller.getMethod().name);
+        caller = caller.getCallerPosition();
       }
     }
     if (prologueEnd) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index dfd4f1c..30fb21f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -579,7 +579,7 @@
 
     public boolean hasOuterPosition(DexMethod method) {
       return (caller == null && callee == method)
-          || (caller != null && caller.getOutermostCaller().method == method);
+          || (caller != null && caller.getOutermostCaller().getMethod() == method);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index 7a8a3f6..bdb2633 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.utils.InternalOptions;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
@@ -183,17 +184,17 @@
     assert !position.equals(emittedPosition);
     if (startLine == NO_LINE_INFO) {
       assert emittedPosition.isNone();
-      if (position.synthetic && position.callerPosition == null) {
+      if (position.isSyntheticPosition() && !position.hasCallerPosition()) {
         // Ignore synthetic positions prior to any actual position.
         // We do need to preserve synthetic position establishing the stack frame for inlined
         // methods.
         return;
       }
-      startLine = position.line;
+      startLine = position.getLine();
       emittedPosition =
-          Position.builder()
-              .setLine(position.line)
-              .setMethod(position.getOutermostCaller().method)
+          SourcePosition.builder()
+              .setLine(position.getLine())
+              .setMethod(position.getOutermostCaller().getMethod())
               .build();
     }
     assert emittedPc != pc;
@@ -235,21 +236,22 @@
     assert previousPc >= 0;
     int pcDelta = nextPc - previousPc;
     assert !previousPosition.isNone() || nextPosition.isNone();
-    assert nextPosition.isNone() || nextPosition.line >= 0;
-    int lineDelta = nextPosition.isNone() ? 0 : nextPosition.line - previousPosition.line;
+    assert nextPosition.isNone() || nextPosition.getLine() >= 0;
+    int lineDelta = nextPosition.isNone() ? 0 : nextPosition.getLine() - previousPosition.getLine();
     assert pcDelta >= 0;
-    if (nextPosition.file != previousPosition.file) {
-      events.add(factory.createSetFile(nextPosition.file));
+    if (nextPosition.getFile() != previousPosition.getFile()) {
+      events.add(factory.createSetFile(nextPosition.getFile()));
     }
     // The LineNumberOptimizer maps new positions based on the outer most caller with
     // callerPosition == null.
-    assert null != nextPosition.callerPosition
-        || null != previousPosition.callerPosition
-        || nextPosition.method == previousPosition.method
+    assert nextPosition.hasCallerPosition()
+        || previousPosition.hasCallerPosition()
+        || nextPosition.getMethod() == previousPosition.getMethod()
         || optimizingLineNumbers;
-    if (nextPosition.callerPosition != previousPosition.callerPosition
-        || nextPosition.method != previousPosition.method) {
-      events.add(factory.createSetInlineFrame(nextPosition.method, nextPosition.callerPosition));
+    if (nextPosition.getCallerPosition() != previousPosition.getCallerPosition()
+        || nextPosition.getMethod() != previousPosition.getMethod()) {
+      events.add(
+          factory.createSetInlineFrame(nextPosition.getMethod(), nextPosition.getCallerPosition()));
     }
     if (lineDelta < Constants.DBG_LINE_BASE
         || lineDelta - Constants.DBG_LINE_BASE >= Constants.DBG_LINE_RANGE) {
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 0aaa24e..f111c9e 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -57,6 +57,7 @@
 import com.android.tools.r8.ir.code.Monitor;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
@@ -1007,7 +1008,7 @@
       if (debugParsingOptions.lineInfo) {
         instructions.add(
             new CfPosition(
-                getLabel(start), Position.builder().setLine(line).setMethod(method).build()));
+                getLabel(start), SourcePosition.builder().setLine(line).setMethod(method).build()));
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
index e68cc71..66251ba 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.analysis.value.SingleConstValue;
 import com.android.tools.r8.ir.analysis.value.SingleDexItemBasedStringValue;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 import com.android.tools.r8.utils.IntBox;
@@ -110,8 +111,14 @@
     ImmutableList.Builder<CfInstruction> instructionBuilder = ImmutableList.builder();
 
     // Set position.
-    Position callerPosition = Position.synthetic(0, syntheticMethodReference, null);
-    Position calleePosition = Position.synthetic(0, originalMethodReference, callerPosition);
+    Position callerPosition =
+        SyntheticPosition.builder().setLine(0).setMethod(syntheticMethodReference).build();
+    Position calleePosition =
+        SyntheticPosition.builder()
+            .setLine(0)
+            .setMethod(originalMethodReference)
+            .setCallerPosition(callerPosition)
+            .build();
     CfPosition position = new CfPosition(new CfLabel(), calleePosition);
     instructionBuilder.add(position);
     instructionBuilder.add(position.getLabel());
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
index 8372926..085276f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
@@ -132,7 +133,8 @@
     public CfCode build(DexMethod syntheticMethodReference) {
       // Building the instructions will adjust maxStack and maxLocals. Build it here before invoking
       // the CfCode constructor to ensure that the value passed in is the updated values.
-      Position callerPosition = Position.synthetic(0, syntheticMethodReference, null);
+      Position callerPosition =
+          SyntheticPosition.builder().setLine(0).setMethod(syntheticMethodReference).build();
       List<CfInstruction> instructions = buildInstructions(callerPosition);
       return new CfCode(
           syntheticMethodReference.getHolderType(),
@@ -201,7 +203,8 @@
     public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
       assert !classInitializers.isEmpty();
 
-      Position callerPosition = Position.synthetic(0, syntheticMethodReference, null);
+      Position callerPosition =
+          SyntheticPosition.builder().setLine(0).setMethod(syntheticMethodReference).build();
       IRMetadata metadata = new IRMetadata();
       NumberGenerator blockNumberGenerator = new NumberGenerator();
       NumberGenerator valueNumberGenerator = new NumberGenerator();
diff --git a/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
index ad16c77..85467b7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
@@ -5,6 +5,8 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.function.Supplier;
@@ -36,7 +38,7 @@
           methodIsSynthesized
               ? callerPosition
               : getCanonical(
-                  Position.builder()
+                  SourcePosition.builder()
                       .setLine(0)
                       .setMethod(method)
                       .setCallerPosition(callerPosition)
@@ -44,7 +46,8 @@
     } else {
       this.callerPosition = null;
       isCompilerSynthesizedInlinee = false;
-      preamblePosition = getCanonical(Position.synthetic(0, method, null));
+      preamblePosition =
+          getCanonical(SyntheticPosition.builder().setLine(0).setMethod(method).build());
     }
   }
 
@@ -81,7 +84,11 @@
     Position callerOfCaller = canonicalizeCallerPosition(caller.callerPosition);
     return getCanonical(
         caller.isNone()
-            ? Position.noneWithMethod(caller.method, callerOfCaller)
+            ? SourcePosition.builder()
+                .setMethod(caller.method)
+                .setCallerPosition(callerOfCaller)
+                .disableLineCheck()
+                .build()
             : caller.builderWithCopy().setCallerPosition(callerOfCaller).build());
   }
 
@@ -106,7 +113,11 @@
         syntheticPosition =
             (min == Integer.MAX_VALUE)
                 ? getPreamblePosition()
-                : Position.synthetic(min < max ? min - 1 : min, originalMethod, callerPosition);
+                : SyntheticPosition.builder()
+                    .setLine(min < max ? min - 1 : min)
+                    .setMethod(originalMethod)
+                    .setCallerPosition(callerPosition)
+                    .build();
       } else {
         // If in release mode we explicitly associate a synthetic none position with monitor exit.
         // This is safe as the runtime must never throw at this position because the monitor cannot
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index c347f87..d4d76da 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -42,7 +42,7 @@
 
   @Override
   public void buildDex(DexBuilder builder) {
-    assert getPosition().isSome() && !getPosition().synthetic;
+    assert getPosition().isSome() && !getPosition().isSyntheticPosition();
     builder.addDebugPosition(this);
   }
 
@@ -84,7 +84,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    assert getPosition().isSome() && !getPosition().synthetic;
+    assert getPosition().isSome() && !getPosition().isSyntheticPosition();
     // All redundant debug positions are removed. Remaining ones must force a pc advance.
     builder.add(new CfNop());
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index cb2ff68..25cbfdf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -7,114 +7,110 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.utils.structural.Equatable;
+import com.android.tools.r8.utils.structural.HashCodeVisitor;
 import com.android.tools.r8.utils.structural.StructuralItem;
 import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
-import com.google.common.annotations.VisibleForTesting;
-import java.util.Objects;
 
-public class Position implements StructuralItem<Position> {
+public abstract class Position implements StructuralItem<Position> {
 
-  // A no-position marker. Not having a position means the position is implicitly defined by the
-  // context, e.g., the marker does not materialize anything concrete.
-  private static final Position NO_POSITION = new Position(-1, null, null, null, false, false);
+  // Compare ID(s) for positions.
+  private static final int SOURCE_POSITION_COMPARE_ID = 1;
+  private static final int SYNTHETIC_POSITION_COMPARE_ID = 2;
 
-  // A synthetic marker position that should never materialize.
-  // This is used specifically to mark exceptional exit blocks from synchronized methods in release.
-  private static final Position NO_POSITION_SYNTHETIC =
-      new Position(-1, null, null, null, true, false);
-
-  // Fake position to use for representing an actual position in testing code.
-  private static final Position TESTING_POSITION = new Position(0, null, null, null, true, false);
-
-  public final int line;
-  public final DexString file;
-  public final boolean synthetic;
+  protected final int line;
+  protected final DexMethod method;
 
   // If there's no inlining, callerPosition is null.
   //
   // For an inlined instruction its Position contains the inlinee's line and method and
   // callerPosition is the position of the invoke instruction in the caller.
+  protected final Position callerPosition;
 
-  public final DexMethod method;
-  public final Position callerPosition;
-  public final boolean removeInnerFrameIfThrowingNpe;
-
-  private static void specify(StructuralSpecification<Position, ?> spec) {
-    spec.withInt(p -> p.line)
-        .withNullableItem(p -> p.file)
-        .withBool(p -> p.synthetic)
-        .withNullableItem(p -> p.method)
-        .withNullableItem(p -> p.callerPosition);
-  }
+  private final boolean removeInnerFramesIfThrowingNpe;
 
   private Position(
-      int line,
-      DexString file,
-      DexMethod method,
-      Position callerPosition,
-      boolean synthetic,
-      boolean removeInnerFrameIfThrowingNpe) {
+      int line, DexMethod method, Position callerPosition, boolean removeInnerFramesIfThrowingNpe) {
     this.line = line;
-    this.file = file;
-    this.synthetic = synthetic;
     this.method = method;
     this.callerPosition = callerPosition;
-    this.removeInnerFrameIfThrowingNpe = removeInnerFrameIfThrowingNpe;
-    assert callerPosition == null || callerPosition.method != null;
+    this.removeInnerFramesIfThrowingNpe = removeInnerFramesIfThrowingNpe;
   }
 
-  public static Position synthetic(int line, DexMethod method, Position callerPosition) {
-    assert line >= 0;
-    assert method != null;
-    return new Position(line, null, method, callerPosition, true, false);
+  public boolean isSyntheticPosition() {
+    return false;
   }
 
-  public static Position none() {
-    return NO_POSITION;
+  public boolean isAdditionalMappingInfoPosition() {
+    return false;
   }
 
-  public static Position syntheticNone() {
-    return NO_POSITION_SYNTHETIC;
-  }
-
-  @VisibleForTesting
-  public static Position testingPosition() {
-    return TESTING_POSITION;
-  }
-
-  // This factory method is used by the Inliner to create Positions when the caller has no valid
-  // positions. Since the callee still may have valid positions we need a non-null Position to set
-  // it as the caller of the inlined Positions.
-  public static Position noneWithMethod(DexMethod method, Position callerPosition) {
-    assert method != null;
-    return new Position(-1, null, method, callerPosition, false, false);
-  }
-
-  public static Position getPositionForInlining(
-      AppView<?> appView, InvokeMethod invoke, ProgramMethod context) {
-    Position position = invoke.getPosition();
-    if (position.method == null) {
-      assert position.isNone();
-      position = Position.noneWithMethod(context.getReference(), null);
-    }
-    assert position.getOutermostCaller().method
-        == appView.graphLens().getOriginalMethodSignature(context.getReference());
-    return position;
+  public boolean isRemoveInnerFramesIfThrowingNpe() {
+    return removeInnerFramesIfThrowingNpe;
   }
 
   public boolean hasCallerPosition() {
     return callerPosition != null;
   }
 
+  public Position getCallerPosition() {
+    return callerPosition;
+  }
+
+  public int getLine() {
+    return line;
+  }
+
+  public DexMethod getMethod() {
+    return method;
+  }
+
+  public static Position none() {
+    return SourcePosition.NO_POSITION;
+  }
+
+  public boolean hasFile() {
+    return false;
+  }
+
+  public DexString getFile() {
+    return null;
+  }
+
   @Override
   public Position self() {
     return this;
   }
 
+  // Unique id to determine the ordering of positions
+  public abstract int getCompareToId();
+
   @Override
-  public StructuralMapping<Position> getStructuralMapping() {
-    return Position::specify;
+  public abstract StructuralMapping<Position> getStructuralMapping();
+
+  private static void specifyBasePosition(StructuralSpecification<Position, ?> spec) {
+    spec.withInt(Position::getCompareToId)
+        .withInt(Position::getLine)
+        .withNullableItem(Position::getMethod)
+        .withNullableItem(Position::getCallerPosition)
+        .withBool(Position::isRemoveInnerFramesIfThrowingNpe);
+  }
+
+  public static Position syntheticNone() {
+    return SyntheticPosition.NO_POSITION_SYNTHETIC;
+  }
+
+  public static Position getPositionForInlining(
+      AppView<?> appView, InvokeMethod invoke, ProgramMethod context) {
+    Position position = invoke.getPosition();
+    if (position.method == null) {
+      assert position.isNone();
+      position = SourcePosition.builder().setMethod(context.getReference()).build();
+    }
+    assert position.getOutermostCaller().method
+        == appView.graphLens().getOriginalMethodSignature(context.getReference());
+    return position;
   }
 
   public boolean isNone() {
@@ -122,7 +118,7 @@
   }
 
   public boolean isSyntheticNone() {
-    return this == NO_POSITION_SYNTHETIC;
+    return this == syntheticNone();
   }
 
   public boolean isSome() {
@@ -139,10 +135,6 @@
     return lastPosition;
   }
 
-  public Position getCallerPosition() {
-    return callerPosition;
-  }
-
   public Position withOutermostCallerPosition(Position newOutermostCallerPosition) {
     return builderWithCopy()
         .setCallerPosition(
@@ -153,14 +145,13 @@
   }
 
   @Override
-  public boolean equals(Object other) {
-    return other instanceof Position && compareTo((Position) other) == 0;
+  public final boolean equals(Object other) {
+    return Equatable.equalsImpl(this, other);
   }
 
   @Override
-  public int hashCode() {
-    return Objects.hash(
-        line, file, synthetic, method, callerPosition, removeInnerFrameIfThrowingNpe);
+  public final int hashCode() {
+    return HashCodeVisitor.run(this);
   }
 
   private String toString(boolean forceMethod) {
@@ -168,8 +159,8 @@
       return "--";
     }
     StringBuilder builder = new StringBuilder();
-    if (file != null) {
-      builder.append(file).append(":");
+    if (hasFile()) {
+      builder.append(getFile()).append(":");
     }
     builder.append("#").append(line);
     if (method != null && (forceMethod || callerPosition != null)) {
@@ -190,64 +181,196 @@
     return toString(false);
   }
 
-  public static Builder builder() {
-    return new Builder();
-  }
+  public abstract PositionBuilder<?, ?> builderWithCopy();
 
-  public Builder builderWithCopy() {
-    return new Builder()
-        .setLine(line)
-        .setFile(file)
-        .setMethod(method)
-        .setCallerPosition(callerPosition)
-        .setSynthetic(synthetic)
-        .setRemoveInnerFramesIfThrowingNpe(removeInnerFrameIfThrowingNpe);
-  }
+  public abstract static class PositionBuilder<
+      P extends Position, B extends PositionBuilder<P, B>> {
 
-  public static class Builder {
+    protected int line = -1;
+    protected DexMethod method;
+    protected Position callerPosition;
+    protected boolean removeInnerFramesIfThrowingNpe;
 
-    public int line;
-    public DexString file;
-    public boolean synthetic;
-    public DexMethod method;
-    public Position callerPosition;
-    public boolean removeInnerFrameIfThrowingNpe;
+    protected boolean noCheckOfPosition;
+    protected boolean noCheckOfMethod;
 
-    public Builder setLine(int line) {
+    abstract B self();
+
+    public B setLine(int line) {
       this.line = line;
-      return this;
+      return self();
     }
 
-    public Builder setFile(DexString file) {
-      this.file = file;
-      return this;
-    }
-
-    public Builder setSynthetic(boolean synthetic) {
-      this.synthetic = synthetic;
-      return this;
-    }
-
-    public Builder setMethod(DexMethod method) {
+    public B setMethod(DexMethod method) {
       this.method = method;
-      return this;
+      return self();
     }
 
-    public Builder setCallerPosition(Position callerPosition) {
+    public B setCallerPosition(Position callerPosition) {
       this.callerPosition = callerPosition;
-      return this;
+      return self();
     }
 
-    public Builder setRemoveInnerFramesIfThrowingNpe(boolean removeInnerFramesIfThrowingNpe) {
-      this.removeInnerFrameIfThrowingNpe = removeInnerFramesIfThrowingNpe;
-      return this;
+    public B setRemoveInnerFramesIfThrowingNpe(boolean removeInnerFramesIfThrowingNpe) {
+      this.removeInnerFramesIfThrowingNpe = removeInnerFramesIfThrowingNpe;
+      return self();
     }
 
-    public Position build() {
-      assert line >= 0;
-      assert method != null;
-      return new Position(
-          line, file, method, callerPosition, synthetic, removeInnerFrameIfThrowingNpe);
+    public B disableLineCheck() {
+      noCheckOfPosition = true;
+      return self();
+    }
+
+    public B disableMethodCheck() {
+      noCheckOfMethod = true;
+      return self();
+    }
+
+    public abstract P build();
+  }
+
+  public static class SourcePosition extends Position {
+
+    // A no-position marker. Not having a position means the position is implicitly defined by the
+    // context, e.g., the marker does not materialize anything concrete.
+    private static final SourcePosition NO_POSITION =
+        new SourcePosition(-1, null, null, false, null);
+
+    public final DexString file;
+
+    private static void specify(StructuralSpecification<Position, ?> spec) {
+      spec.withSpec(Position::specifyBasePosition).withNullableItem(Position::getFile);
+    }
+
+    private SourcePosition(
+        int line,
+        DexMethod method,
+        Position callerPosition,
+        boolean removeInnerFramesIfThrowingNpe,
+        DexString file) {
+      super(line, method, callerPosition, removeInnerFramesIfThrowingNpe);
+      this.file = file;
+      assert callerPosition == null || callerPosition.method != null;
+    }
+
+    @Override
+    public boolean hasFile() {
+      return file != null;
+    }
+
+    @Override
+    public DexString getFile() {
+      return file;
+    }
+
+    @Override
+    public int getCompareToId() {
+      return SOURCE_POSITION_COMPARE_ID;
+    }
+
+    @Override
+    public PositionBuilder<?, ?> builderWithCopy() {
+      return builder()
+          .setLine(line)
+          .setFile(file)
+          .setMethod(method)
+          .setCallerPosition(callerPosition);
+    }
+
+    @Override
+    public StructuralMapping<Position> getStructuralMapping() {
+      return SourcePosition::specify;
+    }
+
+    public static SourcePositionBuilder builder() {
+      return new SourcePositionBuilder();
+    }
+
+    public static class SourcePositionBuilder
+        extends PositionBuilder<SourcePosition, SourcePositionBuilder> {
+
+      private DexString file;
+
+      @Override
+      SourcePositionBuilder self() {
+        return this;
+      }
+
+      public SourcePositionBuilder setFile(DexString file) {
+        this.file = file;
+        return this;
+      }
+
+      @Override
+      public SourcePosition build() {
+        assert noCheckOfPosition || line >= 0;
+        assert noCheckOfMethod || method != null;
+        return new SourcePosition(
+            line, method, callerPosition, removeInnerFramesIfThrowingNpe, file);
+      }
+    }
+  }
+
+  public static class SyntheticPosition extends Position {
+
+    // A synthetic marker position that should never materialize.
+    // This is used specifically to mark exceptional exit blocks from synchronized methods in
+    // release.
+    private static final Position NO_POSITION_SYNTHETIC =
+        new SyntheticPosition(-1, null, null, false);
+
+    private static void specify(StructuralSpecification<Position, ?> spec) {
+      spec.withSpec(Position::specifyBasePosition);
+    }
+
+    private SyntheticPosition(
+        int line,
+        DexMethod method,
+        Position callerPosition,
+        boolean removeInnerFramesIfThrowingNpe) {
+      super(line, method, callerPosition, removeInnerFramesIfThrowingNpe);
+    }
+
+    @Override
+    public boolean isSyntheticPosition() {
+      return true;
+    }
+
+    @Override
+    public int getCompareToId() {
+      return SYNTHETIC_POSITION_COMPARE_ID;
+    }
+
+    @Override
+    public PositionBuilder<?, ?> builderWithCopy() {
+      return builder().setLine(line).setMethod(method).setCallerPosition(callerPosition);
+    }
+
+    @Override
+    public StructuralMapping<Position> getStructuralMapping() {
+      return SyntheticPosition::specify;
+    }
+
+    public static SyntheticPositionBuilder builder() {
+      return new SyntheticPositionBuilder();
+    }
+
+    public static class SyntheticPositionBuilder
+        extends PositionBuilder<SyntheticPosition, SyntheticPositionBuilder> {
+
+      private SyntheticPositionBuilder() {}
+
+      @Override
+      SyntheticPositionBuilder self() {
+        return this;
+      }
+
+      @Override
+      public SyntheticPosition build() {
+        assert noCheckOfPosition || line >= 0;
+        assert noCheckOfMethod || method != null;
+        return new SyntheticPosition(line, method, callerPosition, removeInnerFramesIfThrowingNpe);
+      }
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 0085b70..b358828 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -519,7 +519,9 @@
             && position != currentPosition
             // Ignore synthetic positions prior to any actual position, except when inlined
             // (that is, callerPosition != null).
-            && !(currentPosition.isNone() && position.synthetic && position.callerPosition == null)
+            && !(currentPosition.isNone()
+                && position.isSyntheticPosition()
+                && !position.hasCallerPosition())
             && (appView.options().debug || instruction.instructionTypeCanThrow());
     if (!didLocalsChange && !didPositionChange) {
       return;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 5abeb6e..1a39011 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -904,7 +904,7 @@
         position
             .builderWithCopy()
             .setCallerPosition(
-                canonicalPositions.canonicalizeCallerPosition(position.callerPosition))
+                canonicalPositions.canonicalizeCallerPosition(position.getCallerPosition()))
             .build());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 4181373..f2c1d8d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -43,6 +43,7 @@
 import com.android.tools.r8.ir.code.CanonicalPositions;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -255,10 +256,10 @@
   private Position getCanonicalPositionAppendCaller(DexDebugEntry entry) {
     // If this instruction has already been inlined then this.method must be the outermost caller.
     assert entry.callerPosition == null
-        || entry.callerPosition.getOutermostCaller().method == originalMethod;
+        || entry.callerPosition.getOutermostCaller().getMethod() == originalMethod;
 
     return canonicalPositions.getCanonical(
-        Position.builder()
+        SourcePosition.builder()
             .setLine(entry.line)
             .setFile(entry.sourceFile)
             .setMethod(entry.method)
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 40c7bb9..b6fcd8c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -829,7 +829,9 @@
           } else {
             current = position;
           }
-        } else if (position.isSome() && !position.synthetic && !position.equals(current)) {
+        } else if (position.isSome()
+            && !position.isSyntheticPosition()
+            && !position.equals(current)) {
           DebugPosition positionChange = new DebugPosition();
           positionChange.setPosition(position);
           it.previous();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index cdc2c6e..bcd27e6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -79,6 +79,7 @@
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Switch;
 import com.android.tools.r8.ir.code.Throw;
@@ -3673,7 +3674,8 @@
     InstructionListIterator iterator = block.listIterator(code);
 
     // Attach some synthetic position to all inserted code.
-    Position position = Position.synthetic(1, method.getReference(), null);
+    Position position =
+        SyntheticPosition.builder().setLine(1).setMethod(method.getReference()).build();
     iterator.setInsertionPosition(position);
 
     // Split arguments into their own block.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
index 31801c6..a0d7ede 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
@@ -47,6 +47,7 @@
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.Rem;
 import com.android.tools.r8.ir.code.Sub;
 import com.android.tools.r8.ir.code.Value;
@@ -1537,7 +1538,7 @@
 
     OutlineSourceCode(Outline outline, DexMethod method) {
       this.outline = outline;
-      this.position = Position.synthetic(0, method, null);
+      this.position = SyntheticPosition.builder().setLine(0).setMethod(method).build();
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index ae06b60..c81383c 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.DexSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -64,7 +65,12 @@
       this.paramRegisters[i] = nextRegister(ValueType.fromDexType(params[i]));
     }
 
-    position = Position.synthetic(0, originalMethod, callerPosition);
+    position =
+        SyntheticPosition.builder()
+            .setLine(0)
+            .setMethod(originalMethod)
+            .setCallerPosition(callerPosition)
+            .build();
   }
 
   protected final void add(Consumer<IRBuilder> constructor) {
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 391f2c7..ea756b5 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -375,9 +375,9 @@
             + " Thus, not all identifier strings flowing to that " + kind
             + " are renamed, which can cause resolution failures at runtime.";
     StringDiagnostic diagnostic =
-        instruction.getPosition().line >= 1
+        instruction.getPosition().getLine() >= 1
             ? new StringDiagnostic(
-                message, origin, new TextPosition(0L, instruction.getPosition().line, 1))
+                message, origin, new TextPosition(0L, instruction.getPosition().getLine(), 1))
             : new StringDiagnostic(message, origin);
     appView.options().reporter.warning(diagnostic);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 2a4db34..6dd9775 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -39,6 +39,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser;
 import com.android.tools.r8.kotlin.KotlinSourceDebugExtensionParser.Result;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -103,12 +104,12 @@
 
     @Override
     public Pair<Position, Position> createRemappedPosition(Position position) {
-      assert position.method != null;
-      if (previousMethod == position.method) {
+      assert position.getMethod() != null;
+      if (previousMethod == position.getMethod()) {
         assert previousSourceLine >= 0;
-        if (position.line > previousSourceLine
-            && position.line - previousSourceLine <= maxLineDelta) {
-          nextOptimizedLineNumber += (position.line - previousSourceLine) - 1;
+        if (position.getLine() > previousSourceLine
+            && position.getLine() - previousSourceLine <= maxLineDelta) {
+          nextOptimizedLineNumber += (position.getLine() - previousSourceLine) - 1;
         }
       }
 
@@ -118,8 +119,8 @@
               .setLine(nextOptimizedLineNumber++)
               .setCallerPosition(null)
               .build();
-      previousSourceLine = position.line;
-      previousMethod = position.method;
+      previousSourceLine = position.getLine();
+      previousMethod = position.getMethod();
       return new Pair<>(position, newPosition);
     }
   }
@@ -149,8 +150,8 @@
     @Override
     public Pair<Position, Position> createRemappedPosition(Position position) {
       assert currentMethod != null;
-      int line = position.line;
-      Result parsedData = getAndParseSourceDebugExtension(position.method.holder);
+      int line = position.getLine();
+      Result parsedData = getAndParseSourceDebugExtension(position.getMethod().holder);
       if (parsedData == null) {
         return baseRemapper.createRemappedPosition(position);
       }
@@ -183,7 +184,7 @@
                 factory.createString(methodName),
                 factory.createString(returnTypeDescriptor),
                 argumentDexStringDescriptors);
-        if (!inlinee.equals(position.method)) {
+        if (!inlinee.equals(position.getMethod())) {
           // We have an inline from a different method than the current position.
           Entry<Integer, KotlinSourceDebugExtensionParser.Position> calleePosition =
               parsedData.lookupCalleePosition(line);
@@ -196,7 +197,7 @@
                     .build();
           }
           return baseRemapper.createRemappedPosition(
-              Position.builder()
+              SourcePosition.builder()
                   .setLine(originalInlineeLine)
                   .setMethod(inlinee)
                   .setCallerPosition(position)
@@ -256,9 +257,8 @@
 
     private void emitPositionEvents(int currentPc, Position currentPosition) {
       if (previousPosition == null) {
-        startLine = currentPosition.line;
-        previousPosition =
-            Position.builder().setLine(startLine).setFile(null).setMethod(method).build();
+        startLine = currentPosition.getLine();
+        previousPosition = SourcePosition.builder().setLine(startLine).setMethod(method).build();
       }
       DexDebugEventBuilder.emitAdvancementEvents(
           previousPc,
@@ -509,10 +509,10 @@
               lastMappedRange =
                   classNamingBuilder.addMappedRange(
                       obfuscatedRange,
-                      getOriginalMethodSignature.apply(caller.method),
-                      new Range(Math.max(caller.line, 0)), // Prevent against "no-position".
+                      getOriginalMethodSignature.apply(caller.getMethod()),
+                      new Range(Math.max(caller.getLine(), 0)), // Prevent against "no-position".
                       obfuscatedName);
-              if (caller.removeInnerFrameIfThrowingNpe) {
+              if (caller.isRemoveInnerFramesIfThrowingNpe()) {
                 lastMappedRange.addMappingInformation(
                     RewriteFrameMappingInformation.builder()
                         .addCondition(
@@ -523,7 +523,7 @@
                         .build(),
                     Unreachable::raise);
               }
-              caller = caller.callerPosition;
+              caller = caller.getCallerPosition();
             }
             for (MappingInformation info : methodMappingInfo) {
               lastMappedRange.addMappingInformation(info, Unreachable::raise);
@@ -597,7 +597,7 @@
         if (!(instruction instanceof CfPosition)) {
           continue;
         }
-        return ((CfPosition) instruction).getPosition().line;
+        return ((CfPosition) instruction).getPosition().getLine();
       }
     }
     return 0;
@@ -748,7 +748,7 @@
             super.visit(defaultEvent);
             assert getCurrentLine() >= 0;
             Position position =
-                Position.builder()
+                SourcePosition.builder()
                     .setLine(getCurrentLine())
                     .setFile(getCurrentFile())
                     .setMethod(getCurrentMethod())
@@ -856,7 +856,7 @@
             }
             lastPosition.setFirst(getCurrentPc());
             lastPosition.setSecond(
-                Position.builder()
+                SourcePosition.builder()
                     .setLine(getCurrentLine())
                     .setFile(getCurrentFile())
                     .setMethod(getCurrentMethod())
@@ -932,7 +932,10 @@
     Position newPosition = remappedPosition.getSecond();
     mappedPositions.add(
         new MappedPosition(
-            oldPosition.method, oldPosition.line, oldPosition.callerPosition, newPosition.line));
+            oldPosition.getMethod(),
+            oldPosition.getLine(),
+            oldPosition.getCallerPosition(),
+            newPosition.getLine()));
     return newPosition;
   }
 
@@ -947,7 +950,10 @@
     for (int currentPc = startPc; currentPc < endPc; currentPc++) {
       mappedPositions.add(
           new MappedPosition(
-              oldPosition.method, oldPosition.line, oldPosition.callerPosition, currentPc));
+              oldPosition.getMethod(),
+              oldPosition.getLine(),
+              oldPosition.getCallerPosition(),
+              currentPc));
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/graph/DexDebugEventsTest.java b/src/test/java/com/android/tools/r8/graph/DexDebugEventsTest.java
index 4f71913..9b85698 100644
--- a/src/test/java/com/android/tools/r8/graph/DexDebugEventsTest.java
+++ b/src/test/java/com/android/tools/r8/graph/DexDebugEventsTest.java
@@ -8,7 +8,7 @@
 
 import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
 import com.android.tools.r8.graph.DexDebugEvent.Default;
-import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SourcePosition;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
@@ -133,9 +133,9 @@
     List<DexDebugEvent> events = new ArrayList<>();
     DexDebugEventBuilder.emitAdvancementEvents(
         pc,
-        Position.builder().setLine(line).setMethod(method).build(),
+        SourcePosition.builder().setLine(line).setMethod(method).build(),
         nextPc,
-        Position.builder().setLine(nextLine).setMethod(method).build(),
+        SourcePosition.builder().setLine(nextLine).setMethod(method).build(),
         events,
         factory,
         false);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index 2485f20..2d40272 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -75,7 +76,7 @@
     block.setNumber(basicBlockNumberGenerator.next());
 
     IRMetadata metadata = IRMetadata.unknown();
-    Position position = Position.testingPosition();
+    Position position = SyntheticPosition.builder().setLine(0).build();
 
     Value v3 = new Value(3, TypeElement.getLong(), null);
     v3.setNeedsRegister(true);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 9fc16f8..762f5e9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Throw;
 import com.android.tools.r8.ir.code.Value;
@@ -63,7 +64,7 @@
     // block2:
     //   return
     final NumberGenerator basicBlockNumberGenerator = new NumberGenerator();
-    Position position = Position.testingPosition();
+    Position position = SyntheticPosition.builder().setLine(0).build();
     BasicBlock block2 = new BasicBlock();
     BasicBlock block0 =
         BasicBlock.createGotoBlock(basicBlockNumberGenerator.next(), position, metadata, block2);
@@ -127,7 +128,7 @@
     // block3:
     //   goto block3
     final NumberGenerator basicBlockNumberGenerator = new NumberGenerator();
-    Position position = Position.testingPosition();
+    Position position = SyntheticPosition.builder().setLine(0).build();
     BasicBlock block0 = new BasicBlock();
     block0.setNumber(basicBlockNumberGenerator.next());
     BasicBlock block2 = new BasicBlock();
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 80863b5..664e68e 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -57,6 +57,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.SyntheticPosition;
 import com.android.tools.r8.ir.code.ValueTypeConstraint;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.SourceCode;
@@ -902,7 +903,12 @@
     private final Position position;
 
     public ReturnVoidCode(DexMethod method, Position callerPosition) {
-      this.position = Position.synthetic(0, method, callerPosition);
+      this.position =
+          SyntheticPosition.builder()
+              .setLine(0)
+              .setMethod(method)
+              .setCallerPosition(callerPosition)
+              .build();
     }
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 7c882b4..40fcc0f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -258,7 +258,7 @@
         new Object2IntOpenHashMap<>(code.getInstructions().size());
     for (CfInstruction insn : code.getInstructions()) {
       if (insn instanceof CfPosition) {
-        currentLine = ((CfPosition) insn).getPosition().line;
+        currentLine = ((CfPosition) insn).getPosition().getLine();
       }
       if (currentLine != -1) {
         lineNumberTable.put(new CfInstructionSubject(insn, this), currentLine);