Remove 'id' field from infos, compute an id during serialization (#59)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4b9042..f9a4cf4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 0.6.0-dev.0.0
+
+* The fields `Info.id` and `Info.serializedId` have been removed. These
+  properties were only used for serialization and deserialization. Those values
+  are now computed during the serialization process instead.
+
 ## 0.5.17
 
 * Make `live_code_size_analysis` print library URIs and not library names.
diff --git a/lib/info.dart b/lib/info.dart
index 8bc7e4c..a3cb639 100644
--- a/lib/info.dart
+++ b/lib/info.dart
@@ -27,14 +27,6 @@
   /// Name of the element associated with this info.
   String name;
 
-  /// An id to uniquely identify this info among infos of the same [kind].
-  // TODO(kevmoo) Consider removing `id` entirely. Make it an impl detail of
-  //              the JSON encoder
-  int get id;
-
-  /// A globally unique id combining [kind] and [id] together.
-  String get serializedId;
-
   /// Id used by the compiler when instrumenting code for code coverage.
   // TODO(sigmund): It would be nice if we could use the same id for
   // serialization and for coverage. Could we unify them?
@@ -53,46 +45,12 @@
 // TODO(sigmund): add more:
 //  - inputSize: bytes used in the Dart source program
 abstract class BasicInfo implements Info {
-  static final Set<int> _ids = new Set<int>();
-
-  /// Frees internal cache used for id uniqueness.
-  static void resetIds() => BasicInfo._ids.clear();
-
   final InfoKind kind;
 
-  int _id;
-  // TODO(kevmoo) Make computation of id explicit and not on-demand.
-  int get id {
-    if (_id == null) {
-      assert(this is LibraryInfo ||
-          this is ConstantInfo ||
-          this is OutputUnitInfo ||
-          this.parent != null);
-
-      if (this is ConstantInfo) {
-        // No name and no parent, so `longName` isn't helpful
-        assert(this.name == null);
-        assert(this.parent == null);
-        assert((this as ConstantInfo).code != null);
-        // Instead, use the content of the code.
-        _id = (this as ConstantInfo).code.hashCode;
-      } else {
-        _id = longName(this, useLibraryUri: true, forId: true).hashCode;
-      }
-      while (!_ids.add(_id)) {
-        _id++;
-      }
-    }
-
-    return _id;
-  }
-
   String coverageId;
   int size;
   Info parent;
 
-  String get serializedId => '${kindToString(kind)}/$id';
-
   String name;
 
   /// If using deferred libraries, where the element associated with this info
@@ -101,18 +59,16 @@
 
   BasicInfo(this.kind, this.name, this.outputUnit, this.size, this.coverageId);
 
-  BasicInfo._fromId(String serializedId)
-      : kind = _kindFromSerializedId(serializedId),
-        _id = _idFromSerializedId(serializedId);
+  BasicInfo._(this.kind);
 
-  String toString() => '$serializedId $name [$size]';
+  String toString() => '$kind $name [$size]';
 }
 
 /// Info associated with elements containing executable code (like fields and
 /// methods)
 abstract class CodeInfo implements Info {
   /// How does this function or field depend on others.
-  final Set<DependencyInfo> uses = new SplayTreeSet<DependencyInfo>();
+  final List<DependencyInfo> uses = [];
 }
 
 /// The entire information produced while compiling a program.
@@ -250,7 +206,7 @@
   LibraryInfo(String name, this.uri, OutputUnitInfo outputUnit, int size)
       : super(InfoKind.library, name, outputUnit, size, null);
 
-  LibraryInfo._(String serializedId) : super._fromId(serializedId);
+  LibraryInfo._() : super._(InfoKind.library);
 
   T accept<T>(InfoVisitor<T> visitor) => visitor.visitLibrary(this);
 }
@@ -265,7 +221,7 @@
   OutputUnitInfo(String name, int size)
       : super(InfoKind.outputUnit, name, null, size, null);
 
-  OutputUnitInfo._(String serializedId) : super._fromId(serializedId);
+  OutputUnitInfo._() : super._(InfoKind.outputUnit);
 
   T accept<T>(InfoVisitor<T> visitor) => visitor.visitOutput(this);
 }
@@ -288,7 +244,7 @@
       {String name, this.isAbstract, OutputUnitInfo outputUnit, int size: 0})
       : super(InfoKind.clazz, name, outputUnit, size, null);
 
-  ClassInfo._(String serializedId) : super._fromId(serializedId);
+  ClassInfo._() : super._(InfoKind.clazz);
 
   T accept<T>(InfoVisitor<T> visitor) => visitor.visitClass(this);
 }
@@ -303,7 +259,7 @@
   ConstantInfo({int size: 0, this.code, OutputUnitInfo outputUnit})
       : super(InfoKind.constant, null, outputUnit, size, null);
 
-  ConstantInfo._(String serializedId) : super._fromId(serializedId);
+  ConstantInfo._() : super._(InfoKind.constant);
 
   T accept<T>(InfoVisitor<T> visitor) => visitor.visitConstant(this);
 }
@@ -340,7 +296,7 @@
       this.isConst})
       : super(InfoKind.field, name, outputUnit, size, coverageId);
 
-  FieldInfo._(String serializedId) : super._fromId(serializedId);
+  FieldInfo._() : super._(InfoKind.field);
 
   T accept<T>(InfoVisitor<T> visitor) => visitor.visitField(this);
 }
@@ -353,7 +309,7 @@
   TypedefInfo(String name, this.type, OutputUnitInfo outputUnit)
       : super(InfoKind.typedef, name, outputUnit, 0, null);
 
-  TypedefInfo._(String serializedId) : super._fromId(serializedId);
+  TypedefInfo._() : super._(InfoKind.typedef);
 
   T accept<T>(InfoVisitor<T> visitor) => visitor.visitTypedef(this);
 }
@@ -417,7 +373,7 @@
       this.measurements})
       : super(InfoKind.function, name, outputUnit, size, coverageId);
 
-  FunctionInfo._(String serializedId) : super._fromId(serializedId);
+  FunctionInfo._() : super._(InfoKind.function);
 
   T accept<T>(InfoVisitor<T> visitor) => visitor.visitFunction(this);
 }
@@ -431,13 +387,13 @@
       {String name, OutputUnitInfo outputUnit, int size: 0, this.function})
       : super(InfoKind.closure, name, outputUnit, size, null);
 
-  ClosureInfo._(String serializedId) : super._fromId(serializedId);
+  ClosureInfo._() : super._(InfoKind.closure);
 
   T accept<T>(InfoVisitor<T> visitor) => visitor.visitClosure(this);
 }
 
 /// Information about how a dependency is used.
-class DependencyInfo implements Comparable<DependencyInfo> {
+class DependencyInfo {
   /// The dependency, either a FunctionInfo or FieldInfo.
   final Info target;
 
@@ -447,21 +403,6 @@
   final String mask;
 
   DependencyInfo(this.target, this.mask);
-
-  int compareTo(DependencyInfo other) {
-    var value = target.serializedId.compareTo(other.target.serializedId);
-    if (value == 0) {
-      value = mask.compareTo(other.mask);
-    }
-    return value;
-  }
-
-  bool operator ==(other) =>
-      other is DependencyInfo &&
-      target.serializedId == other.target.serializedId &&
-      mask == other.mask;
-
-  int get hashCode => target.serializedId.hashCode * 37 ^ mask.hashCode;
 }
 
 /// Name and type information about a function parameter.
@@ -522,12 +463,6 @@
   }
 }
 
-int _idFromSerializedId(String serializedId) =>
-    int.parse(serializedId.substring(serializedId.indexOf('/') + 1));
-
-InfoKind _kindFromSerializedId(String serializedId) =>
-    kindFromString(serializedId.substring(0, serializedId.indexOf('/')));
-
 InfoKind kindFromString(String kind) {
   switch (kind) {
     case 'library':
diff --git a/lib/json_info_codec.dart b/lib/json_info_codec.dart
index ea4b52e..ec0e6c1 100644
--- a/lib/json_info_codec.dart
+++ b/lib/json_info_codec.dart
@@ -5,8 +5,8 @@
 /// Converters and codecs for converting between JSON and [Info] classes.
 part of dart2js_info.info;
 
-List<String> _toSortedSerializIds(Iterable<Info> infos) =>
-    infos.map((i) => i.serializedId).toList()..sort(compareNatural);
+List<String> _toSortedSerializedIds(Iterable<Info> infos, Map<Info, Id> ids) =>
+    infos.map((i) => ids[i].serializedId).toList()..sort(compareNatural);
 
 // TODO(sigmund): add unit tests.
 class JsonToAllInfoConverter extends Converter<Map<String, dynamic>, AllInfo> {
@@ -41,19 +41,11 @@
     result.constants.addAll(
         (elements['constant'] as Map).values.map((c) => parseConstant(c)));
 
-    var idMap = new HashMap<String, Info>();
-    for (var f in result.functions) {
-      idMap[f.serializedId] = f;
-    }
-    for (var f in result.fields) {
-      idMap[f.serializedId] = f;
-    }
-
     json['holding'].forEach((k, deps) {
-      var src = idMap[k];
+      var src = registry[k];
       assert(src != null);
       for (var dep in deps) {
-        var target = idMap[dep['id']];
+        var target = registry[dep['id']];
         assert(target != null);
         (src as CodeInfo).uses.add(new DependencyInfo(target, dep['mask']));
       }
@@ -61,7 +53,7 @@
 
     json['dependencies']?.forEach((String k, dependencies) {
       List<String> deps = dependencies;
-      result.dependencies[idMap[k]] = deps.map((d) => idMap[d]).toList();
+      result.dependencies[registry[k]] = deps.map((d) => registry[d]).toList();
     });
 
     result.outputUnits
@@ -310,21 +302,21 @@
     }
     return registry.putIfAbsent(serializedId, () {
       if (serializedId.startsWith('function/')) {
-        return new FunctionInfo._(serializedId);
+        return new FunctionInfo._();
       } else if (serializedId.startsWith('closure/')) {
-        return new ClosureInfo._(serializedId);
+        return new ClosureInfo._();
       } else if (serializedId.startsWith('library/')) {
-        return new LibraryInfo._(serializedId);
+        return new LibraryInfo._();
       } else if (serializedId.startsWith('class/')) {
-        return new ClassInfo._(serializedId);
+        return new ClassInfo._();
       } else if (serializedId.startsWith('field/')) {
-        return new FieldInfo._(serializedId);
+        return new FieldInfo._();
       } else if (serializedId.startsWith('constant/')) {
-        return new ConstantInfo._(serializedId);
+        return new ConstantInfo._();
       } else if (serializedId.startsWith('typedef/')) {
-        return new TypedefInfo._(serializedId);
+        return new TypedefInfo._();
       } else if (serializedId.startsWith('outputUnit/')) {
-        return new OutputUnitInfo._(serializedId);
+        return new OutputUnitInfo._();
       }
       assert(false);
     });
@@ -333,13 +325,43 @@
 
 class AllInfoToJsonConverter extends Converter<AllInfo, Map>
     implements InfoVisitor<Map> {
+  final Map<Info, Id> ids = new HashMap<Info, Id>();
+  final Set<int> usedIds = new Set<int>();
+
+  Id idFor(Info info) {
+    var serializedId = ids[info];
+    if (serializedId != null) return serializedId;
+
+    assert(info is LibraryInfo ||
+        info is ConstantInfo ||
+        info is OutputUnitInfo ||
+        info.parent != null);
+
+    int id;
+    if (info is ConstantInfo) {
+      // No name and no parent, so `longName` isn't helpful
+      assert(info.name == null);
+      assert(info.parent == null);
+      assert(info.code != null);
+      // Instead, use the content of the code.
+      id = info.code.hashCode;
+    } else {
+      id = longName(info, useLibraryUri: true, forId: true).hashCode;
+    }
+    while (!usedIds.add(id)) {
+      id++;
+    }
+    serializedId = new Id(info.kind, id);
+    return ids[info] = serializedId;
+  }
+
   Map convert(AllInfo info) => info.accept(this);
 
   Map _visitList(List<Info> infos) {
     // Using SplayTree to maintain a consistent order of keys
     var map = new SplayTreeMap<String, Map>(compareNatural);
     for (var info in infos) {
-      map['${info.id}'] = info.accept(this);
+      map['${idFor(info).id}'] = info.accept(this);
     }
     return map;
   }
@@ -364,13 +386,16 @@
   }
 
   Map _visitDependencyInfo(DependencyInfo info) =>
-      {'id': info.target.serializedId, 'mask': info.mask};
+      {'id': idFor(info.target).serializedId, 'mask': info.mask};
 
   Map _visitAllInfoHolding(AllInfo allInfo) {
     var map = new SplayTreeMap<String, List>(compareNatural);
     void helper(CodeInfo info) {
       if (info.uses.isEmpty) return;
-      map[info.serializedId] = info.uses.map(_visitDependencyInfo).toList();
+      map[idFor(info).serializedId] = info.uses
+          .map(_visitDependencyInfo)
+          .toList()
+            ..sort((a, b) => a['id'].compareTo(b['id']));
     }
 
     allInfo.functions.forEach(helper);
@@ -381,7 +406,7 @@
   Map _visitAllInfoDependencies(AllInfo allInfo) {
     var map = new SplayTreeMap<String, List>(compareNatural);
     allInfo.dependencies.forEach((k, v) {
-      map[k.serializedId] = _toSortedSerializIds(v);
+      map[idFor(k).serializedId] = _toSortedSerializedIds(v, ids);
     });
     return map;
   }
@@ -404,7 +429,7 @@
 
   Map visitProgram(ProgramInfo info) {
     return {
-      'entrypoint': info.entrypoint.serializedId,
+      'entrypoint': idFor(info.entrypoint),
       'size': info.size,
       'dart2jsVersion': info.dart2jsVersion,
       'compilationMoment': '${info.compilationMoment}',
@@ -422,7 +447,7 @@
 
   Map _visitBasicInfo(BasicInfo info) {
     var res = {
-      'id': info.serializedId,
+      'id': idFor(info).serializedId,
       'kind': kindToString(info.kind),
       'name': info.name,
       'size': info.size,
@@ -430,22 +455,24 @@
     // TODO(sigmund): Omit this also when outputUnit.id == 0 (most code is in
     // the main output unit by default).
     if (info.outputUnit != null) {
-      res['outputUnit'] = info.outputUnit.serializedId;
+      res['outputUnit'] = idFor(info.outputUnit).serializedId;
     }
     if (info.coverageId != null) res['coverageId'] = info.coverageId;
-    if (info.parent != null) res['parent'] = info.parent.serializedId;
+    if (info.parent != null) res['parent'] = idFor(info.parent).serializedId;
     return res;
   }
 
   Map visitLibrary(LibraryInfo info) {
     return _visitBasicInfo(info)
       ..addAll(<String, Object>{
-        'children': _toSortedSerializIds([
-          info.topLevelFunctions,
-          info.topLevelVariables,
-          info.classes,
-          info.typedefs
-        ].expand((i) => i)),
+        'children': _toSortedSerializedIds(
+            [
+              info.topLevelFunctions,
+              info.topLevelVariables,
+              info.classes,
+              info.typedefs
+            ].expand((i) => i),
+            ids),
         'canonicalUri': '${info.uri}',
       });
   }
@@ -455,15 +482,15 @@
       ..addAll(<String, Object>{
         // TODO(sigmund): change format, include only when abstract is true.
         'modifiers': {'abstract': info.isAbstract},
-        'children':
-            _toSortedSerializIds([info.fields, info.functions].expand((i) => i))
+        'children': _toSortedSerializedIds(
+            [info.fields, info.functions].expand((i) => i), ids)
       });
   }
 
   Map visitField(FieldInfo info) {
     var result = _visitBasicInfo(info)
       ..addAll(<String, Object>{
-        'children': _toSortedSerializIds(info.closures),
+        'children': _toSortedSerializedIds(info.closures, ids),
         'inferredType': info.inferredType,
         'code': info.code,
         'type': info.type,
@@ -471,7 +498,7 @@
     if (info.isConst) {
       result['const'] = true;
       if (info.initializer != null) {
-        result['initializer'] = info.initializer.serializedId;
+        result['initializer'] = idFor(info.initializer).serializedId;
       }
     }
     return result;
@@ -524,7 +551,7 @@
   Map visitFunction(FunctionInfo info) {
     return _visitBasicInfo(info)
       ..addAll(<String, Object>{
-        'children': _toSortedSerializIds(info.closures),
+        'children': _toSortedSerializedIds(info.closures, ids),
         'modifiers': _visitFunctionModifiers(info.modifiers),
         'returnType': info.returnType,
         'inferredReturnType': info.inferredReturnType,
@@ -542,7 +569,7 @@
 
   Map visitClosure(ClosureInfo info) {
     return _visitBasicInfo(info)
-      ..addAll(<String, Object>{'function': info.function.serializedId});
+      ..addAll(<String, Object>{'function': idFor(info.function).serializedId});
   }
 
   visitTypedef(TypedefInfo info) => _visitBasicInfo(info)..['type'] = info.type;
@@ -555,3 +582,12 @@
   final Converter<AllInfo, Map> encoder = new AllInfoToJsonConverter();
   final Converter<Map, AllInfo> decoder = new JsonToAllInfoConverter();
 }
+
+class Id {
+  final InfoKind kind;
+  final int id;
+
+  Id(this.kind, this.id);
+
+  String get serializedId => '$kind/$id';
+}
diff --git a/lib/proto_info_codec.dart b/lib/proto_info_codec.dart
index cbeb35c..db9b6a0 100644
--- a/lib/proto_info_codec.dart
+++ b/lib/proto_info_codec.dart
@@ -9,6 +9,7 @@
 
 import 'info.dart';
 import 'src/proto/info.pb.dart';
+import 'src/util.dart';
 
 export 'src/proto/info.pb.dart';
 
@@ -23,11 +24,42 @@
 }
 
 class AllInfoToProtoConverter extends Converter<AllInfo, AllInfoPB> {
+  final Map<Info, Id> ids = {};
+  final Set<int> usedIds = new Set<int>();
+
+  Id idFor(Info info) {
+    if (info == null) return null;
+    var serializedId = ids[info];
+    if (serializedId != null) return serializedId;
+
+    assert(info is LibraryInfo ||
+        info is ConstantInfo ||
+        info is OutputUnitInfo ||
+        info.parent != null);
+
+    int id;
+    if (info is ConstantInfo) {
+      // No name and no parent, so `longName` isn't helpful
+      assert(info.name == null);
+      assert(info.parent == null);
+      assert(info.code != null);
+      // Instead, use the content of the code.
+      id = info.code.hashCode;
+    } else {
+      id = longName(info, useLibraryUri: true, forId: true).hashCode;
+    }
+    while (!usedIds.add(id)) {
+      id++;
+    }
+    serializedId = new Id(info.kind, id);
+    return ids[info] = serializedId;
+  }
+
   AllInfoPB convert(AllInfo info) => _convertToAllInfoPB(info);
 
-  static DependencyInfoPB _convertToDependencyInfoPB(DependencyInfo info) {
+  DependencyInfoPB _convertToDependencyInfoPB(DependencyInfo info) {
     return new DependencyInfoPB()
-      ..targetId = info.target?.serializedId
+      ..targetId = idFor(info.target)?.serializedId
       ..mask = info.mask;
   }
 
@@ -65,24 +97,28 @@
     return proto;
   }
 
-  static LibraryInfoPB _convertToLibraryInfoPB(LibraryInfo info) {
+  LibraryInfoPB _convertToLibraryInfoPB(LibraryInfo info) {
     final proto = new LibraryInfoPB()..uri = info.uri.toString();
 
     proto.childrenIds
-        .addAll(info.topLevelFunctions.map((func) => func.serializedId));
+        .addAll(info.topLevelFunctions.map((func) => idFor(func).serializedId));
+    proto.childrenIds.addAll(
+        info.topLevelVariables.map((field) => idFor(field).serializedId));
     proto.childrenIds
-        .addAll(info.topLevelVariables.map((field) => field.serializedId));
-    proto.childrenIds.addAll(info.classes.map((clazz) => clazz.serializedId));
-    proto.childrenIds.addAll(info.typedefs.map((def) => def.serializedId));
+        .addAll(info.classes.map((clazz) => idFor(clazz).serializedId));
+    proto.childrenIds
+        .addAll(info.typedefs.map((def) => idFor(def).serializedId));
 
     return proto;
   }
 
-  static ClassInfoPB _convertToClassInfoPB(ClassInfo info) {
+  ClassInfoPB _convertToClassInfoPB(ClassInfo info) {
     final proto = new ClassInfoPB()..isAbstract = info.isAbstract;
 
-    proto.childrenIds.addAll(info.functions.map((func) => func.serializedId));
-    proto.childrenIds.addAll(info.fields.map((field) => field.serializedId));
+    proto.childrenIds
+        .addAll(info.functions.map((func) => idFor(func).serializedId));
+    proto.childrenIds
+        .addAll(info.fields.map((field) => idFor(field).serializedId));
 
     return proto;
   }
@@ -96,7 +132,7 @@
       ..isExternal = modifiers.isExternal;
   }
 
-  static FunctionInfoPB _convertToFunctionInfoPB(FunctionInfo info) {
+  FunctionInfoPB _convertToFunctionInfoPB(FunctionInfo info) {
     final proto = new FunctionInfoPB()
       ..functionModifiers = _convertToFunctionModifiers(info.modifiers)
       ..inlinedCount = info.inlinedCount ?? 0;
@@ -122,13 +158,13 @@
     }
 
     proto.childrenIds
-        .addAll(info.closures.map(((closure) => closure.serializedId)));
+        .addAll(info.closures.map(((closure) => idFor(closure).serializedId)));
     proto.parameters.addAll(info.parameters.map(_convertToParameterInfoPB));
 
     return proto;
   }
 
-  static FieldInfoPB _convertToFieldInfoPB(FieldInfo info) {
+  FieldInfoPB _convertToFieldInfoPB(FieldInfo info) {
     final proto = new FieldInfoPB()
       ..type = info.type
       ..inferredType = info.inferredType
@@ -139,11 +175,11 @@
     }
 
     if (info.initializer != null) {
-      proto.initializerId = info.initializer.serializedId;
+      proto.initializerId = idFor(info.initializer).serializedId;
     }
 
     proto.childrenIds
-        .addAll(info.closures.map((closure) => closure.serializedId));
+        .addAll(info.closures.map((closure) => idFor(closure).serializedId));
 
     return proto;
   }
@@ -162,14 +198,14 @@
     return new TypedefInfoPB()..type = info.type;
   }
 
-  static ClosureInfoPB _convertToClosureInfoPB(ClosureInfo info) {
-    return new ClosureInfoPB()..functionId = info.function.serializedId;
+  ClosureInfoPB _convertToClosureInfoPB(ClosureInfo info) {
+    return new ClosureInfoPB()..functionId = idFor(info.function).serializedId;
   }
 
-  static InfoPB _convertToInfoPB(Info info) {
+  InfoPB _convertToInfoPB(Info info) {
     final proto = new InfoPB()
-      ..id = info.id
-      ..serializedId = info.serializedId
+      ..id = idFor(info).id
+      ..serializedId = idFor(info).serializedId
       ..size = info.size;
 
     if (info.name != null) {
@@ -177,7 +213,7 @@
     }
 
     if (info.parent != null) {
-      proto.parentId = info.parent.serializedId;
+      proto.parentId = idFor(info.parent).serializedId;
     }
 
     if (info.coverageId != null) {
@@ -188,7 +224,7 @@
       // TODO(lorenvs): Similar to the JSON codec, omit this for the default
       // output unit. At the moment, there is no easy way to identify which
       // output unit is the default on [OutputUnitInfo].
-      proto.outputUnitId = info.outputUnit.serializedId;
+      proto.outputUnitId = idFor(info.outputUnit).serializedId;
     }
 
     if (info is CodeInfo) {
@@ -216,9 +252,9 @@
     return proto;
   }
 
-  static ProgramInfoPB _convertToProgramInfoPB(ProgramInfo info) {
+  ProgramInfoPB _convertToProgramInfoPB(ProgramInfo info) {
     return new ProgramInfoPB()
-      ..entrypointId = info.entrypoint.serializedId
+      ..entrypointId = idFor(info.entrypoint).serializedId
       ..size = info.size
       ..dart2jsVersion = info.dart2jsVersion
       ..compilationMoment =
@@ -234,8 +270,8 @@
       ..minified = info.minified ?? false;
   }
 
-  static Iterable<AllInfoPB_AllInfosEntry>
-      _convertToAllInfosEntries<T extends Info>(Iterable<T> infos) sync* {
+  Iterable<AllInfoPB_AllInfosEntry> _convertToAllInfosEntries<T extends Info>(
+      Iterable<T> infos) sync* {
     for (final info in infos) {
       final infoProto = _convertToInfoPB(info);
       final entry = new AllInfoPB_AllInfosEntry()
@@ -261,7 +297,7 @@
     return proto;
   }
 
-  static AllInfoPB _convertToAllInfoPB(AllInfo info) {
+  AllInfoPB _convertToAllInfoPB(AllInfo info) {
     final proto = new AllInfoPB()
       ..program = _convertToProgramInfoPB(info.program);
 
@@ -291,3 +327,12 @@
   final Converter<AllInfo, AllInfoPB> encoder = new AllInfoToProtoConverter();
   final Converter<AllInfoPB, AllInfo> decoder = new ProtoToAllInfoConverter();
 }
+
+class Id {
+  final InfoKind kind;
+  final int id;
+
+  Id(this.kind, this.id);
+
+  String get serializedId => '$kind/$id';
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 29f99fa..1a12162 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: dart2js_info
-version: 0.5.17
+version: 0.6.0-dev
 
 description: >
   Libraries and tools to process data produced when running dart2js with