Unify handling of scoped and global mapping information.

Change-Id: I6e1f0eb64d52d9ec6ebe1004c4276ed97fb35e78
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 14c2179..70ca4dd 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -16,9 +16,9 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
-import com.android.tools.r8.naming.mappinginformation.ScopedMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.ScopedMappingInformation.ScopeReference;
 import com.android.tools.r8.position.Position;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.BiMapContainer;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 import com.android.tools.r8.utils.Reporter;
@@ -39,6 +39,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.function.Consumer;
 
 public class ClassNameMapper implements ProguardMap {
 
@@ -65,10 +66,18 @@
     }
 
     @Override
-    void addScopedMappingInformation(ScopedMappingInformation scopedMappingInformation) {
-      scopedMappingInformation.forEach(
-          (ref, info) ->
-              scopedMappingInfo.computeIfAbsent(ref, ignoreArgument(ArrayList::new)).add(info));
+    public void addMappingInformation(
+        ScopeReference reference,
+        MappingInformation info,
+        Consumer<MappingInformation> onProhibitedAddition) {
+      List<MappingInformation> additionalMappings =
+          scopedMappingInfo.computeIfAbsent(reference, ignoreArgument(ArrayList::new));
+      for (MappingInformation existing : additionalMappings) {
+        if (!existing.allowOther(info)) {
+          onProhibitedAddition.accept(existing);
+        }
+      }
+      additionalMappings.add(info);
     }
 
     @Override
@@ -87,9 +96,11 @@
     private ImmutableMap<String, ClassNamingForNameMapper> buildClassNameMappings() {
       // Ensure that all scoped references have at least the identity in the final mapping.
       for (ScopeReference reference : scopedMappingInfo.keySet()) {
-        mapping.computeIfAbsent(
-            reference.getHolderReference().getTypeName(),
-            t -> ClassNamingForNameMapper.builder(t, t));
+        if (!reference.isGlobalScope()) {
+          mapping.computeIfAbsent(
+              reference.getHolderReference().getTypeName(),
+              t -> ClassNamingForNameMapper.builder(t, t));
+        }
       }
       ImmutableMap.Builder<String, ClassNamingForNameMapper> builder = ImmutableMap.builder();
       builder.orderEntriesByValue(Comparator.comparing(x -> x.originalName));
@@ -266,7 +277,10 @@
     // deterministic (and easy to navigate manually).
     assert verifyIsSorted();
     for (ClassNamingForNameMapper naming : getClassNameMappings().values()) {
-      naming.write(consumer);
+      List<MappingInformation> additionalMappingInfo =
+          getAdditionalMappingInfo(
+              ScopeReference.fromClassReference(Reference.classFromTypeName(naming.renamedName)));
+      naming.write(consumer, additionalMappingInfo);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNaming.java b/src/main/java/com/android/tools/r8/naming/ClassNaming.java
index 5a282d0..429ed45 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNaming.java
@@ -3,9 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.MemberNaming.Signature;
-import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.utils.ThrowingConsumer;
 
 /**
@@ -19,13 +17,6 @@
 
     public abstract Builder addMemberEntry(MemberNaming entry);
 
-    public abstract Builder addMappingInformation(MappingInformation mappingInformation);
-
-    public abstract Builder addMappingInformation(
-        MappingInformation mappingInformation,
-        DiagnosticsHandler diagnosticsHandler,
-        int lineNumber);
-
     public abstract ClassNaming build();
 
     /** This is an optional method, may be implemented as no-op */
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
index acbb577..c8e2f2b 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -11,7 +10,6 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind;
-import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThrowingConsumer;
@@ -78,21 +76,6 @@
     }
 
     @Override
-    public ClassNaming.Builder addMappingInformation(MappingInformation mappingInformation) {
-      // Intentionally kept empty until we support additional information with -applymapping.
-      return this;
-    }
-
-    @Override
-    public ClassNaming.Builder addMappingInformation(
-        MappingInformation mappingInformation,
-        DiagnosticsHandler diagnosticsHandler,
-        int lineNumber) {
-      // Intentionally kept empty until we support additional information with -applymapping.
-      return this;
-    }
-
-    @Override
     public ClassNamingForMapApplier build() {
       return new ClassNamingForMapApplier(
           renamedName, originalName, position, qualifiedMethodMembers, methodMembers, fieldMembers);
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index fb084f1..523184b 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -3,16 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
-import com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 import com.android.tools.r8.utils.ThrowingConsumer;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import java.util.ArrayList;
@@ -23,7 +20,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.function.Consumer;
 
 /**
  * Stores name information for a class.
@@ -40,7 +36,6 @@
     private final Map<FieldSignature, MemberNaming> fieldMembers = Maps.newHashMap();
     private final Map<String, List<MappedRange>> mappedRangesByName = Maps.newHashMap();
     private final Map<String, List<MemberNaming>> mappedFieldNamingsByName = Maps.newHashMap();
-    private final List<MappingInformation> additionalMappings = new ArrayList<>();
 
     private Builder(String renamedName, String originalName) {
       this.originalName = originalName;
@@ -61,39 +56,6 @@
     }
 
     @Override
-    public ClassNaming.Builder addMappingInformation(MappingInformation mappingInformation) {
-      return addMappingInformation(
-          mappingInformation,
-          other -> {
-            assert false;
-          });
-    }
-
-    @Override
-    public ClassNaming.Builder addMappingInformation(
-        MappingInformation mappingInformation,
-        DiagnosticsHandler diagnosticsHandler,
-        int lineNumber) {
-      return addMappingInformation(
-          mappingInformation,
-          other ->
-              diagnosticsHandler.warning(
-                  MappingInformationDiagnostics.notAllowedCombination(
-                      originalName, renamedName, mappingInformation, other, lineNumber)));
-    }
-
-    private ClassNaming.Builder addMappingInformation(
-        MappingInformation mappingInformation, Consumer<MappingInformation> notAllowedCombination) {
-      for (MappingInformation information : additionalMappings) {
-        if (!information.allowOther(mappingInformation)) {
-          notAllowedCombination.accept(information);
-        }
-      }
-      additionalMappings.add(mappingInformation);
-      return this;
-    }
-
-    @Override
     public ClassNamingForNameMapper build() {
       Map<String, MappedRangesOfName> map;
 
@@ -107,13 +69,7 @@
       }
 
       return new ClassNamingForNameMapper(
-          renamedName,
-          originalName,
-          methodMembers,
-          fieldMembers,
-          map,
-          mappedFieldNamingsByName,
-          ImmutableList.copyOf(additionalMappings));
+          renamedName, originalName, methodMembers, fieldMembers, map, mappedFieldNamingsByName);
     }
 
     /** The parameters are forwarded to MappedRange constructor, see explanation there. */
@@ -244,24 +200,19 @@
 
   public final Map<String, List<MemberNaming>> mappedFieldNamingsByName;
 
-  private final ImmutableList<MappingInformation> additionalMappings;
-
   private ClassNamingForNameMapper(
       String renamedName,
       String originalName,
       Map<MethodSignature, MemberNaming> methodMembers,
       Map<FieldSignature, MemberNaming> fieldMembers,
       Map<String, MappedRangesOfName> mappedRangesByRenamedName,
-      Map<String, List<MemberNaming>> mappedFieldNamingsByName,
-      ImmutableList<MappingInformation> additionalMappings) {
+      Map<String, List<MemberNaming>> mappedFieldNamingsByName) {
     this.renamedName = renamedName;
     this.originalName = originalName;
     this.methodMembers = ImmutableMap.copyOf(methodMembers);
     this.fieldMembers = ImmutableMap.copyOf(fieldMembers);
     this.mappedRangesByRenamedName = mappedRangesByRenamedName;
     this.mappedFieldNamingsByName = mappedFieldNamingsByName;
-    assert additionalMappings != null;
-    this.additionalMappings = additionalMappings;
   }
 
   public MappedRangesOfName getMappedRangesForRenamedName(String renamedName) {
@@ -346,11 +297,11 @@
     return methodMembers.values();
   }
 
-  void write(ChainableStringConsumer consumer) {
+  void write(ChainableStringConsumer consumer, List<MappingInformation> additionalMappingInfo) {
     consumer.accept(originalName).accept(" -> ").accept(renamedName).accept(":\n");
 
     // Print all additional mapping information.
-    additionalMappings.forEach(info -> consumer.accept("# " + info.serialize()).accept("\n"));
+    additionalMappingInfo.forEach(info -> consumer.accept("# " + info.serialize()).accept("\n"));
 
     // Print field member namings.
     forAllFieldNaming(m -> consumer.accept("    ").accept(m.toString()).accept("\n"));
@@ -367,14 +318,10 @@
     }
   }
 
-  public List<MappingInformation> getAdditionalMappings() {
-    return additionalMappings;
-  }
-
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
-    write(ChainableStringConsumer.wrap(builder::append));
+    write(ChainableStringConsumer.wrap(builder::append), Collections.emptyList());
     return builder.toString();
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMap.java b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
index f56e3b8..ee8303f 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMap.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
@@ -4,8 +4,10 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.naming.mappinginformation.ScopedMappingInformation;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.naming.mappinginformation.ScopedMappingInformation.ScopeReference;
 import com.android.tools.r8.position.Position;
+import java.util.function.Consumer;
 
 public interface ProguardMap {
 
@@ -15,7 +17,10 @@
 
     abstract ProguardMap build();
 
-    abstract void addScopedMappingInformation(ScopedMappingInformation scopedMappingInformation);
+    abstract void addMappingInformation(
+        ScopeReference scope,
+        MappingInformation MappingInformation,
+        Consumer<MappingInformation> onProhibitedAddition);
   }
 
   boolean hasMapping(DexType type);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index d7ab9c8..c03cb20 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.ProguardMap.Builder;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics;
 import com.android.tools.r8.naming.mappinginformation.MetaInfMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.ScopedMappingInformation.ClassScopeReference;
 import com.android.tools.r8.naming.mappinginformation.ScopedMappingInformation.ScopeReference;
@@ -24,6 +25,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
+import java.util.function.BiConsumer;
 
 /**
  * Parses a Proguard mapping file and produces mappings from obfuscated class names to the original
@@ -88,7 +90,7 @@
   private int lineOffset = 0;
   private String line;
   private MapVersion version = MapVersion.MapVersionNone;
-  private ScopeReference implicitSingletonScope = null;
+  private ScopeReference implicitSingletonScope = ScopeReference.globalScope();
 
   private int peekCodePoint() {
     return lineOffset < line.length() ? line.codePointAt(lineOffset) : '\n';
@@ -224,12 +226,17 @@
   // Parsing of entries
 
   private void parseClassMappings(ProguardMap.Builder mapBuilder) throws IOException {
+    assert implicitSingletonScope == ScopeReference.globalScope();
     while (hasLine()) {
       skipWhitespace();
       if (isCommentLineWithJsonBrace()) {
-        // TODO(b/179665169): Parse the mapping information without doing anything with it, since we
-        //  at this point do not have a global context.
-        parseMappingInformation();
+        parseMappingInformation(
+            (reference, info) -> {
+              if (!reference.isGlobalScope()) {
+                diagnosticsHandler.error(
+                    MappingInformationDiagnostics.invalidScopeFor(lineNo, reference, info));
+              }
+            });
         // Skip reading the rest of the line.
         lineOffset = line.length();
         nextLine();
@@ -260,18 +267,23 @@
     }
   }
 
-  private MappingInformation parseMappingInformation() {
+  private void parseMappingInformation(
+      BiConsumer<ScopeReference, MappingInformation> onMappingInfo) {
     MappingInformation info =
         MappingInformation.fromJsonObject(
             version, parseJsonInComment(), diagnosticsHandler, lineNo, implicitSingletonScope);
     if (info == null) {
-      return null;
+      return;
     }
     MetaInfMappingInformation generatorInfo = info.asMetaInfMappingInformation();
     if (generatorInfo != null) {
       version = generatorInfo.getMapVersion();
     }
-    return info;
+    if (info.isScopedMappingInformation()) {
+      info.asScopedMappingInformation().forEach(onMappingInfo);
+    } else {
+      onMappingInfo.accept(ScopeReference.globalScope(), info);
+    }
   }
 
   private void parseMemberMappings(Builder mapBuilder, ClassNaming.Builder classNamingBuilder)
@@ -284,14 +296,15 @@
       Range mappedRange = null;
       // Try to parse any information added in comments above member namings
       if (isCommentLineWithJsonBrace()) {
-        MappingInformation mappingInfo = parseMappingInformation();
-        if (mappingInfo != null) {
-          if (mappingInfo.isScopedMappingInformation()) {
-            mapBuilder.addScopedMappingInformation(mappingInfo.asScopedMappingInformation());
-          } else {
-            classNamingBuilder.addMappingInformation(mappingInfo, diagnosticsHandler, lineNo);
-          }
-        }
+        parseMappingInformation(
+            (reference, mappingInfo) ->
+                mapBuilder.addMappingInformation(
+                    reference,
+                    mappingInfo,
+                    conflictingInfo ->
+                        diagnosticsHandler.warning(
+                            MappingInformationDiagnostics.notAllowedCombination(
+                                reference, mappingInfo, conflictingInfo, lineNo))));
         // Skip reading the rest of the line.
         lineOffset = line.length();
         continue;
diff --git a/src/main/java/com/android/tools/r8/naming/SeedMapper.java b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
index 2e5b125..863a2b1 100644
--- a/src/main/java/com/android/tools/r8/naming/SeedMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
@@ -9,7 +9,8 @@
 
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.MemberNaming.Signature;
-import com.android.tools.r8.naming.mappinginformation.ScopedMappingInformation;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.naming.mappinginformation.ScopedMappingInformation.ScopeReference;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.ImmutableMap;
@@ -24,6 +25,7 @@
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * Mappings read from the given ProGuard map.
@@ -62,7 +64,10 @@
     }
 
     @Override
-    void addScopedMappingInformation(ScopedMappingInformation scopedMappingInformation) {
+    void addMappingInformation(
+        ScopeReference scope,
+        MappingInformation MappingInformation,
+        Consumer<MappingInformation> onProhibitedAddition) {
       // Not needed.
     }
 
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/CompilerSynthesizedMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/CompilerSynthesizedMappingInformation.java
index d44e5e6..a49c4de 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/CompilerSynthesizedMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/CompilerSynthesizedMappingInformation.java
@@ -40,6 +40,11 @@
   }
 
   @Override
+  public String getId() {
+    return ID;
+  }
+
+  @Override
   public boolean isCompilerSynthesizedMappingInformation() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/FileNameInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/FileNameInformation.java
index 38eabbf..39d0072 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/FileNameInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/FileNameInformation.java
@@ -6,35 +6,33 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.MapVersion;
+import com.google.common.collect.ImmutableList;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonPrimitive;
 
-public class FileNameInformation extends MappingInformation {
+public class FileNameInformation extends ScopedMappingInformation {
 
   private final String fileName;
 
   public static final String ID = "sourceFile";
   static final String FILE_NAME_KEY = "fileName";
 
-  private FileNameInformation(String fileName) {
-    super(NO_LINE_NUMBER);
+  private FileNameInformation(String fileName, ImmutableList<ScopeReference> scopeReferences) {
+    super(scopeReferences);
     this.fileName = fileName;
   }
 
+  @Override
+  public String getId() {
+    return ID;
+  }
+
   public String getFileName() {
     return fileName;
   }
 
   @Override
-  public String serialize() {
-    JsonObject result = new JsonObject();
-    result.add(MAPPING_ID_KEY, new JsonPrimitive(ID));
-    result.add(FILE_NAME_KEY, new JsonPrimitive(fileName));
-    return result.toString();
-  }
-
-  @Override
   public boolean isFileNameInformation() {
     return true;
   }
@@ -49,23 +47,39 @@
     return !information.isFileNameInformation();
   }
 
-  public static FileNameInformation build(String fileName) {
-    return new FileNameInformation(fileName);
+  public static FileNameInformation build(ScopeReference classScope, String fileName) {
+    return new FileNameInformation(fileName, ImmutableList.of(classScope));
   }
 
-  public static FileNameInformation build(
+  // Hard override of serialize as there is no current support for scope in source-file info.
+  // This should be removed for experimental support of scope in the external format.
+  @Override
+  public String serialize() {
+    return serializeToJsonObject(new JsonObject()).toString();
+  }
+
+  @Override
+  protected JsonObject serializeToJsonObject(JsonObject object) {
+    object.add(MAPPING_ID_KEY, new JsonPrimitive(ID));
+    object.add(FILE_NAME_KEY, new JsonPrimitive(fileName));
+    return object;
+  }
+
+  public static FileNameInformation deserialize(
       MapVersion version,
       JsonObject object,
       DiagnosticsHandler diagnosticsHandler,
-      int lineNumber) {
-    // Source file information is valid for all map file versions.
+      int lineNumber,
+      ScopeReference implicitSingletonScope) {
+    assert implicitSingletonScope instanceof ClassScopeReference;
     try {
       JsonElement fileName =
           getJsonElementFromObject(object, diagnosticsHandler, lineNumber, FILE_NAME_KEY, ID);
       if (fileName == null) {
         return null;
       }
-      return new FileNameInformation(fileName.getAsString());
+      return new FileNameInformation(
+          fileName.getAsString(), ImmutableList.of(implicitSingletonScope));
     } catch (UnsupportedOperationException | IllegalStateException ignored) {
       diagnosticsHandler.info(
           MappingInformationDiagnostics.invalidValueForObjectWithId(lineNumber, FILE_NAME_KEY, ID));
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
index 0ef03d0..beb0b29 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
@@ -22,6 +22,8 @@
     this.lineNumber = lineNumber;
   }
 
+  public abstract String getId();
+
   public int getLineNumber() {
     return lineNumber;
   }
@@ -100,7 +102,8 @@
         return MetaInfMappingInformation.deserialize(
             version, object, diagnosticsHandler, lineNumber);
       case FileNameInformation.ID:
-        return FileNameInformation.build(version, object, diagnosticsHandler, lineNumber);
+        return FileNameInformation.deserialize(
+            version, object, diagnosticsHandler, lineNumber, implicitSingletonScope);
       case CompilerSynthesizedMappingInformation.ID:
         return CompilerSynthesizedMappingInformation.deserialize(
             version, object, diagnosticsHandler, lineNumber, implicitSingletonScope);
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformationDiagnostics.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformationDiagnostics.java
index 6cc0b28..42cb7ba 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformationDiagnostics.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformationDiagnostics.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.naming.mappinginformation.ScopedMappingInformation.ScopeReference;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.position.TextPosition;
@@ -36,6 +37,14 @@
     this.position = position;
   }
 
+  public static MappingInformationDiagnostics invalidScopeFor(
+      int lineNumber, ScopeReference reference, MappingInformation info) {
+    return new MappingInformationDiagnostics(
+        String.format(
+            "Cannot use scope %s for mapping information %s", reference.toString(), info.getId()),
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
   static MappingInformationDiagnostics noHandlerFor(int lineNumber, String value) {
     return new MappingInformationDiagnostics(
         String.format("Could not find a handler for %s", value),
@@ -94,20 +103,14 @@
   }
 
   public static MappingInformationDiagnostics notAllowedCombination(
-      String className,
-      String renamedClassName,
-      MappingInformation one,
-      MappingInformation other,
-      int lineNumber) {
+      ScopeReference reference, MappingInformation one, MappingInformation other, int lineNumber) {
     return new MappingInformationDiagnostics(
         "The mapping '"
             + one.serialize()
             + "' is not allowed in combination with '"
             + other.serialize()
             + "' in the mapping for "
-            + className
-            + " -> "
-            + renamedClassName,
+            + reference.toString(),
         new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MetaInfMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MetaInfMappingInformation.java
index ba6bb72..abe2990 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MetaInfMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MetaInfMappingInformation.java
@@ -24,6 +24,11 @@
   }
 
   @Override
+  public String getId() {
+    return ID;
+  }
+
+  @Override
   public boolean isMetaInfMappingInformation() {
     return true;
   }
@@ -35,7 +40,7 @@
 
   @Override
   public boolean allowOther(MappingInformation information) {
-    return !information.isMetaInfMappingInformation();
+    return true;
   }
 
   public MapVersion getMapVersion() {
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/ScopedMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/ScopedMappingInformation.java
index 4385f5a..e3f43d5 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/ScopedMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/ScopedMappingInformation.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -22,6 +23,10 @@
   // to map to java.lang.String with the post-minification names.
   public abstract static class ScopeReference {
 
+    public static ScopeReference globalScope() {
+      return GlobalScopeReference.INSTANCE;
+    }
+
     public static ScopeReference fromClassReference(ClassReference reference) {
       return new ClassScopeReference(reference);
     }
@@ -34,6 +39,10 @@
       throw new Unimplemented("No support for reference: " + referenceString);
     }
 
+    public boolean isGlobalScope() {
+      return equals(ScopeReference.globalScope());
+    }
+
     public abstract String toReferenceString();
 
     public abstract ClassReference getHolderReference();
@@ -50,6 +59,35 @@
     }
   }
 
+  public static class GlobalScopeReference extends ScopeReference {
+    private static final GlobalScopeReference INSTANCE = new GlobalScopeReference();
+
+    @Override
+    public String toReferenceString() {
+      throw new Unreachable();
+    }
+
+    @Override
+    public String toString() {
+      return "<global-scope>";
+    }
+
+    @Override
+    public ClassReference getHolderReference() {
+      throw new Unreachable();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      return this == other;
+    }
+
+    @Override
+    public int hashCode() {
+      return System.identityHashCode(this);
+    }
+  }
+
   public static class ClassScopeReference extends ScopeReference {
     private final ClassReference reference;
 
@@ -146,7 +184,7 @@
   }
 
   @Override
-  public final String serialize() {
+  public String serialize() {
     JsonObject object = serializeToJsonObject(new JsonObject());
     JsonArray scopeArray = new JsonArray();
     scopeReferences.forEach(ref -> scopeArray.add(ref.toReferenceString()));
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
index 1e9397a..1bd8c4c 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.naming.mappinginformation.ScopedMappingInformation.ScopeReference;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
@@ -29,20 +30,24 @@
 
   private final ClassReference obfuscatedReference;
   private final ClassNamingForNameMapper mapper;
-  private final Retracer retracer;
+  private final RetracerImpl retracer;
 
   private RetraceClassResultImpl(
-      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper, Retracer retracer) {
+      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper, RetracerImpl retracer) {
     this.obfuscatedReference = obfuscatedReference;
     this.mapper = mapper;
     this.retracer = retracer;
   }
 
   static RetraceClassResultImpl create(
-      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper, Retracer retracer) {
+      ClassReference obfuscatedReference, ClassNamingForNameMapper mapper, RetracerImpl retracer) {
     return new RetraceClassResultImpl(obfuscatedReference, mapper, retracer);
   }
 
+  RetracerImpl getRetracerImpl() {
+    return retracer;
+  }
+
   @Override
   public RetraceFieldResultImpl lookupField(String fieldName) {
     return lookupField(FieldDefinition.create(obfuscatedReference, fieldName));
@@ -221,12 +226,13 @@
 
     @Override
     public RetraceSourceFileResultImpl retraceSourceFile(String sourceFile) {
-      if (mapper != null) {
-        for (MappingInformation mappingInformation : mapper.getAdditionalMappings()) {
-          if (mappingInformation.isFileNameInformation()) {
-            return new RetraceSourceFileResultImpl(
-                mappingInformation.asFileNameInformation().getFileName(), false);
-          }
+      for (MappingInformation info :
+          classResult
+              .getRetracerImpl()
+              .getAdditionalMappingInfo(
+                  ScopeReference.fromClassReference(classResult.obfuscatedReference))) {
+        if (info.isFileNameInformation()) {
+          return new RetraceSourceFileResultImpl(info.asFileNameInformation().getFileName(), false);
         }
       }
       return new RetraceSourceFileResultImpl(
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index 03f515b..4c76e3a 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -6,6 +6,8 @@
 
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.naming.mappinginformation.ScopedMappingInformation.ScopeReference;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
@@ -14,13 +16,14 @@
 import com.android.tools.r8.retrace.ProguardMapProducer;
 import com.android.tools.r8.retrace.Retracer;
 import java.io.BufferedReader;
+import java.util.Collection;
 
 /** A default implementation for the retrace api using the ClassNameMapper defined in R8. */
 public class RetracerImpl implements Retracer {
 
   private final ClassNameMapper classNameMapper;
 
-  private RetracerImpl(ClassNameMapper classNameMapper) {
+  public RetracerImpl(ClassNameMapper classNameMapper) {
     this.classNameMapper = classNameMapper;
     assert classNameMapper != null;
   }
@@ -69,4 +72,8 @@
   public RetraceTypeResultImpl retraceType(TypeReference typeReference) {
     return RetraceTypeResultImpl.create(typeReference, this);
   }
+
+  public Collection<MappingInformation> getAdditionalMappingInfo(ScopeReference reference) {
+    return classNameMapper.getAdditionalMappingInfo(reference);
+  }
 }
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 aba61ee..890107b 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -49,7 +50,7 @@
 import com.android.tools.r8.naming.Range;
 import com.android.tools.r8.naming.mappinginformation.CompilerSynthesizedMappingInformation;
 import com.android.tools.r8.naming.mappinginformation.FileNameInformation;
-import com.android.tools.r8.naming.mappinginformation.ScopedMappingInformation.ClassScopeReference;
+import com.android.tools.r8.naming.mappinginformation.ScopedMappingInformation.ScopeReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.retrace.internal.RetraceUtils;
 import com.android.tools.r8.shaking.KeepInfoCollection;
@@ -293,23 +294,28 @@
                       com.android.tools.r8.position.Position.UNKNOWN));
 
       // Check if source file should be added to the map
+      ScopeReference classScope =
+          ScopeReference.fromClassReference(
+              Reference.classFromDescriptor(renamedDescriptor.toString()));
       if (clazz.sourceFile != null) {
         String sourceFile = clazz.sourceFile.toString();
         if (!RetraceUtils.hasPredictableSourceFileName(clazz.toSourceString(), sourceFile)) {
-          Builder builder = onDemandClassNamingBuilder.get();
-          builder.addMappingInformation(FileNameInformation.build(sourceFile));
+          classNameMapperBuilder.addMappingInformation(
+              classScope,
+              FileNameInformation.build(classScope, sourceFile),
+              conflictingInfo -> {
+                throw new Unreachable();
+              });
         }
       }
 
       if (isSyntheticClass && appView.options().testing.enableExperimentalMapFileVersion) {
-        onDemandClassNamingBuilder
-            .get()
-            .addMappingInformation(
-                CompilerSynthesizedMappingInformation.builder()
-                    .addScopeReference(
-                        new ClassScopeReference(
-                            Reference.classFromDescriptor(renamedDescriptor.toString())))
-                    .build());
+        classNameMapperBuilder.addMappingInformation(
+            classScope,
+            CompilerSynthesizedMappingInformation.builder().addScopeReference(classScope).build(),
+            conflictingInfo -> {
+              throw new Unreachable();
+            });
       }
 
       // If the class is renamed add it to the classNamingBuilder.
diff --git a/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java b/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java
index deab412..c2d5e34 100644
--- a/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MapReaderVersionTest.java
@@ -6,6 +6,7 @@
 import static junit.framework.TestCase.assertEquals;
 
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
@@ -53,6 +54,7 @@
 
   @Test
   public void testConcatMapFiles() throws IOException {
+    TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
     ClassNameMapper mapper =
         ClassNameMapper.mapperFromString(
             StringUtils.joinLines(
@@ -67,7 +69,9 @@
                 // concatenates).
                 "# { id: 'com.android.tools.r8.metainf', map-version: 'none' }",
                 "pkg.Baz -> a.c:",
-                "# { id: 'com.android.tools.r8.synthesized' }"));
+                "# { id: 'com.android.tools.r8.synthesized' }"),
+            diagnostics);
+    diagnostics.assertNoMessages();
     assertMapping("a.a", "pkg.Foo", false, mapper);
     assertMapping("a.b", "pkg.Bar", true, mapper);
     assertMapping("a.c", "pkg.Baz", false, mapper);
diff --git a/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java b/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java
index 22fcef4..7d2ec89 100644
--- a/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java
@@ -23,15 +23,13 @@
 @RunWith(Parameterized.class)
 public class DuplicateMappingsTest extends TestBase {
 
-  private final TestParameters parameters;
-
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+    return getTestParameters().withNoneRuntime().build();
   }
 
   public DuplicateMappingsTest(TestParameters parameters) {
-    this.parameters = parameters;
+    parameters.assertNoneRuntime();
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java b/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java
index 9bfcffa..ff4296d 100644
--- a/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java
@@ -62,12 +62,12 @@
         ((stackTrace, inspector) -> {
           assertEquals(FILE_NAME, stackTrace.getStackTraceLines().get(0).fileName);
           assertEquals(
-              1,
+              FILE_NAME,
               inspector
                   .clazz(ClassWithCustomFileName.class)
-                  .getNaming()
-                  .getAdditionalMappings()
-                  .size());
+                  .retraceUnique()
+                  .retraceSourceFile("nofile.java")
+                  .getFilename());
         }));
   }
 
@@ -76,14 +76,15 @@
     runTest(
         true,
         ((stackTrace, inspector) -> {
+          // Since the type has a mapping, the file is inferred from the class name.
           assertEquals("SourceFileTest.java", stackTrace.getStackTraceLines().get(0).fileName);
           assertEquals(
-              0,
+              "SourceFileTest.java",
               inspector
                   .clazz(ClassWithoutCustomFileName.class)
-                  .getNaming()
-                  .getAdditionalMappings()
-                  .size());
+                  .retraceUnique()
+                  .retraceSourceFile("nofile.java")
+                  .getFilename());
         }));
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 491bf9d..0e2de72f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -10,6 +10,8 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.retrace.RetraceClassElement;
+import com.android.tools.r8.retrace.RetraceClassResult;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -213,6 +215,16 @@
   }
 
   @Override
+  public RetraceClassResult retrace() {
+    throw new Unreachable("Cannot retrace an absent class");
+  }
+
+  @Override
+  public RetraceClassElement retraceUnique() {
+    throw new Unreachable("Cannot retrace an absent class");
+  }
+
+  @Override
   public ClassNamingForNameMapper getNaming() {
     return null;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index a794e67..f614059 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -14,6 +14,8 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.retrace.RetraceClassElement;
+import com.android.tools.r8.retrace.RetraceClassResult;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.utils.ListUtils;
 import com.google.common.base.Predicates;
@@ -230,6 +232,10 @@
             descriptor.substring(0, descriptor.length() - 1) + COMPANION_CLASS_NAME_SUFFIX + ";"));
   }
 
+  public abstract RetraceClassResult retrace();
+
+  public abstract RetraceClassElement retraceUnique();
+
   public abstract ClassNamingForNameMapper getNaming();
 
   public abstract String disassembleUsingJavap(boolean verbose) throws Exception;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index b782ae2..caea2a4 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -39,6 +39,7 @@
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.retrace.Retracer;
 import com.android.tools.r8.retrace.internal.DirectClassNameMapperProguardMapProducer;
+import com.android.tools.r8.retrace.internal.RetracerImpl;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.BiMapContainer;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -68,6 +69,7 @@
   private final ClassNameMapper mapping;
   final Map<String, String> originalToObfuscatedMapping;
   final Map<String, String> obfuscatedToOriginalMapping;
+  private Retracer lazyRetracer = null;
 
   public static MethodSignature MAIN =
       new MethodSignature("main", "void", new String[] {"java.lang.String[]"});
@@ -172,6 +174,13 @@
     return dexItemFactory;
   }
 
+  public Retracer getRetracer() {
+    if (lazyRetracer == null) {
+      lazyRetracer = new RetracerImpl(mapping);
+    }
+    return lazyRetracer;
+  }
+
   DexType toDexType(String string) {
     return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(string));
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index c69c4a0..859f58f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -6,6 +6,7 @@
 
 import static com.android.tools.r8.KotlinTestBase.METADATA_TYPE;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.TestRuntime.CfRuntime;
 import com.android.tools.r8.ToolHelper;
@@ -33,6 +34,8 @@
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.retrace.RetraceClassElement;
+import com.android.tools.r8.retrace.RetraceClassResult;
 import com.android.tools.r8.retrace.RetraceTypeResult;
 import com.android.tools.r8.retrace.RetracedFieldReference;
 import com.android.tools.r8.retrace.Retracer;
@@ -46,6 +49,7 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -534,6 +538,25 @@
         codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
   }
 
+  public RetraceClassResult retrace() {
+    assertTrue(mapping.getNaming() != null);
+    return codeInspector
+        .getRetracer()
+        .retraceClass(Reference.classFromTypeName(mapping.getNaming().renamedName));
+  }
+
+  public RetraceClassElement retraceUnique() {
+    RetraceClassResult result = retrace();
+    if (result.isAmbiguous()) {
+      fail("Expected unique retrace of " + this + ", got ambiguous: " + result);
+    }
+    Optional<RetraceClassElement> first = result.stream().findFirst();
+    if (!first.isPresent()) {
+      fail("Expected unique retrace of " + this + ", got empty result");
+    }
+    return first.get();
+  }
+
   @Override
   public ClassNamingForNameMapper getNaming() {
     return mapping.getNaming();