Add ClosureInfo for closures.

This allows us to track closure size, including captured variables,
in addition to the function size.

R=sigmund@google.com

Review URL: https://codereview.chromium.org//2402473002 .
diff --git a/bin/debug_info.dart b/bin/debug_info.dart
index 3c09114..ec39001 100644
--- a/bin/debug_info.dart
+++ b/bin/debug_info.dart
@@ -67,7 +67,7 @@
     _fail('$percent% size missing: $accounted (all libs + consts) '
         '< $realTotal (total)');
   }
-  var missingTotal = tracker.missing.values.fold(0, (a, b) => a + b);
+  int missingTotal = tracker.missing.values.fold(0, (a, b) => a + b);
   if (missingTotal > 0) {
     var percent = (missingTotal * 100 / realTotal).toStringAsFixed(2);
     _fail('$percent% size missing in libraries (sum of elements > lib.size)');
diff --git a/lib/info.dart b/lib/info.dart
index 9661dcc..e25eb43 100644
--- a/lib/info.dart
+++ b/lib/info.dart
@@ -104,6 +104,9 @@
   // and we'll include it only once in the output.
   List<ConstantInfo> constants = <ConstantInfo>[];
 
+  /// Information about closures anywhere in the program.
+  List<ClosureInfo> closures = <ClosureInfo>[];
+
   /// Information about output units (should be just one entry if not using
   /// deferred loading).
   List<OutputUnitInfo> outputUnits = <OutputUnitInfo>[];
@@ -123,7 +126,7 @@
   /// Major version indicating breaking changes in the format. A new version
   /// means that an old deserialization algorithm will not work with the new
   /// format.
-  final int version = 4;
+  final int version = 5;
 
   /// Minor version indicating non-breaking changes in the format. A change in
   /// this version number means that the json parsing in this library from a
@@ -263,7 +266,7 @@
   String inferredType;
 
   /// Nested closures seen in the field initializer.
-  List<FunctionInfo> closures;
+  List<ClosureInfo> closures;
 
   /// The actual generated code for the field.
   String code;
@@ -321,7 +324,7 @@
   FunctionModifiers modifiers;
 
   /// Nested closures that appear within the body of this function.
-  List<FunctionInfo> closures;
+  List<ClosureInfo> closures;
 
   /// The type of this function.
   String type;
@@ -371,6 +374,22 @@
   dynamic accept(InfoVisitor visitor) => visitor.visitFunction(this);
 }
 
+/// Information about a closure, also known as a local function.
+class ClosureInfo extends BasicInfo {
+  static int _ids = 0;
+
+  /// The function that is wrapped by this closure.
+  FunctionInfo function;
+
+  ClosureInfo(
+      {String name, OutputUnitInfo outputUnit, int size: 0, this.function})
+      : super(InfoKind.closure, _ids++, name, outputUnit, size, null);
+
+  ClosureInfo._(String serializedId) : super._fromId(serializedId);
+
+  dynamic accept(InfoVisitor visitor) => visitor.visitClosure(this);
+}
+
 /// Information about how a dependency is used.
 class DependencyInfo {
   /// The dependency, either a FunctionInfo or FieldInfo.
@@ -407,7 +426,7 @@
       this.isExternal: false});
 }
 
-/// Possible values of the `kind` field in the serialied infos.
+/// Possible values of the `kind` field in the serialized infos.
 enum InfoKind {
   library,
   clazz,
@@ -416,6 +435,7 @@
   constant,
   outputUnit,
   typedef,
+  closure,
 }
 
 String kindToString(InfoKind kind) {
@@ -434,6 +454,8 @@
       return 'outputUnit';
     case InfoKind.typedef:
       return 'typedef';
+    case InfoKind.closure:
+      return 'closure';
     default:
       return null;
   }
@@ -461,6 +483,8 @@
       return InfoKind.outputUnit;
     case 'typedef':
       return InfoKind.typedef;
+    case 'closure':
+      return InfoKind.closure;
     default:
       return null;
   }
@@ -476,6 +500,7 @@
   T visitConstant(ConstantInfo info);
   T visitFunction(FunctionInfo info);
   T visitTypedef(TypedefInfo info);
+  T visitClosure(ClosureInfo info);
   T visitOutput(OutputUnitInfo info);
 }
 
@@ -508,15 +533,18 @@
   }
 
   visitField(FieldInfo info) {
-    info.closures.forEach(visitFunction);
+    info.closures.forEach(visitClosure);
   }
 
   visitConstant(ConstantInfo info) {}
 
   visitFunction(FunctionInfo info) {
-    info.closures.forEach(visitFunction);
+    info.closures.forEach(visitClosure);
   }
 
   visitTypedef(TypedefInfo info) {}
   visitOutput(OutputUnitInfo info) {}
+  visitClosure(ClosureInfo info) {
+    visitFunction(info.function);
+  }
 }
diff --git a/lib/json_info_codec.dart b/lib/json_info_codec.dart
index 5176ce3..8382bb0 100644
--- a/lib/json_info_codec.dart
+++ b/lib/json_info_codec.dart
@@ -7,10 +7,10 @@
 
 // TODO(sigmund): add unit tests.
 class JsonToAllInfoConverter extends Converter<Map<String, dynamic>, AllInfo> {
-  Map<String, Info> registry;
+  Map<String, Info> registry = <String, Info>{};
 
   AllInfo convert(Map<String, dynamic> json) {
-    registry = <String, Info>{};
+    registry.clear();
 
     var result = new AllInfo();
     var elements = json['elements'];
@@ -19,15 +19,13 @@
     result.classes.addAll((elements['class'] as Map).values.map(parseClass));
     result.functions
         .addAll((elements['function'] as Map).values.map(parseFunction));
+    result.closures
+        .addAll((elements['closure'] as Map).values.map(parseClosure));
     result.fields.addAll((elements['field'] as Map).values.map(parseField));
     result.typedefs
         .addAll((elements['typedef'] as Map).values.map(parseTypedef));
-
-    // TODO(sigmund): remove null check on next breaking version
-    var constants = elements['constant'];
-    if (constants != null) {
-      result.constants.addAll((constants as Map).values.map(parseConstant));
-    }
+    result.constants
+        .addAll((elements['constant'] as Map).values.map(parseConstant));
 
     var idMap = <String, Info>{};
     for (var f in result.functions) {
@@ -208,11 +206,23 @@
         isExternal: json['external'] == true);
   }
 
+  ClosureInfo parseClosure(Map json) {
+    ClosureInfo result = parseId(json['id']);
+    return result
+      ..name = json['name']
+      ..parent = parseId(json['parent'])
+      ..outputUnit = parseId(json['outputUnit'])
+      ..size = json['size']
+      ..function = parseId(json['function']);
+  }
+
   Info parseId(String serializedId) => registry.putIfAbsent(serializedId, () {
         if (serializedId == null) {
           return null;
         } else if (serializedId.startsWith('function/')) {
           return new FunctionInfo._(serializedId);
+        } else if (serializedId.startsWith('closure/')) {
+          return new ClosureInfo._(serializedId);
         } else if (serializedId.startsWith('library/')) {
           return new LibraryInfo._(serializedId);
         } else if (serializedId.startsWith('class/')) {
@@ -249,13 +259,15 @@
     var jsonTypedefs = _visitList(info.typedefs);
     var jsonFields = _visitList(info.fields);
     var jsonConstants = _visitList(info.constants);
+    var jsonClosures = _visitList(info.closures);
     return {
       'library': jsonLibraries,
       'class': jsonClasses,
       'function': jsonFunctions,
       'typedef': jsonTypedefs,
       'field': jsonFields,
-      'constant': jsonConstants
+      'constant': jsonConstants,
+      'closure': jsonClosures,
     };
   }
 
@@ -433,6 +445,11 @@
       });
   }
 
+  Map visitClosure(ClosureInfo info) {
+    return _visitBasicInfo(info)
+      ..addAll({'function': info.function.serializedId});
+  }
+
   visitTypedef(TypedefInfo info) => _visitBasicInfo(info)..['type'] = info.type;
 
   visitOutput(OutputUnitInfo info) =>
diff --git a/pubspec.yaml b/pubspec.yaml
index a5f9d21..2c7d6e9 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: dart2js_info
-version: 0.3.0
+version: 0.5.0
 description: >
   Libraries and tools to process data produced when running dart2js with
   --dump-info.
diff --git a/test/hello_world/hello_world.js.info.json b/test/hello_world/hello_world.js.info.json
index 33eacf6..2bd859d 100644
--- a/test/hello_world/hello_world.js.info.json
+++ b/test/hello_world/hello_world.js.info.json
@@ -1452,7 +1452,7 @@
         "name": "message",
         "size": 0,
         "outputUnit": "outputUnit/0",
-        "coverageId": "4090",
+        "coverageId": "4087",
         "parent": "class/143",
         "children": [],
         "inferredType": "Value mask: [\"Intercepted function with no arguments.\"] type: [exact=JSString]",
@@ -1802,7 +1802,8 @@
         "type": "E"
       }
     },
-    "constant": {}
+    "constant": {},
+    "closure": {}
   },
   "holding": {
     "function/0": [
@@ -1846,21 +1847,21 @@
       "id": "outputUnit/0",
       "kind": "outputUnit",
       "name": null,
-      "size": 10124,
+      "size": 10116,
       "imports": [
         null
       ]
     }
   ],
-  "dump_version": 4,
+  "dump_version": 5,
   "deferredFiles": {},
   "dump_minor_version": "0",
   "program": {
     "entrypoint": "function/0",
-    "size": 10124,
+    "size": 10116,
     "dart2jsVersion": null,
-    "compilationMoment": "2016-09-30 16:14:58.038530",
-    "compilationDuration": 2726976,
+    "compilationMoment": "2016-10-06 10:14:59.145665",
+    "compilationDuration": 2729236,
     "toJsonDuration": 4000,
     "dumpInfoDuration": 0,
     "noSuchMethodEnabled": false,
diff --git a/test/parse_test.dart b/test/parse_test.dart
index ead1701..8b891dc 100644
--- a/test/parse_test.dart
+++ b/test/parse_test.dart
@@ -19,10 +19,10 @@
       expect(program, isNotNull);
 
       expect(program.entrypoint, isNotNull);
-      expect(program.size, 10124);
+      expect(program.size, 10116);
       expect(program.compilationMoment,
-          DateTime.parse("2016-09-30 16:14:58.038530"));
-      expect(program.compilationDuration, new Duration(microseconds: 2726976));
+          DateTime.parse("2016-10-06 10:14:59.145665"));
+      expect(program.compilationDuration, new Duration(microseconds: 2729236));
       expect(program.toJsonDuration, new Duration(milliseconds: 4));
       expect(program.dumpInfoDuration, new Duration(seconds: 0));
       expect(program.noSuchMethodEnabled, false);