| Index: lib/info.dart
 | 
| diff --git a/lib/info.dart b/lib/info.dart
 | 
| index 27e1fe77a4cf40b1b6138a3116909f7b295d891a..5e79a70e973fd0fbf084ecb364fd985dd61b0635 100644
 | 
| --- a/lib/info.dart
 | 
| +++ b/lib/info.dart
 | 
| @@ -5,9 +5,13 @@
 | 
|  /// Data produced by dart2js when run with the `--dump-info` flag.
 | 
|  library dart2js_info.info;
 | 
|  
 | 
| +import 'dart:convert';
 | 
| +
 | 
|  import 'src/measurements.dart';
 | 
|  export 'src/measurements.dart';
 | 
|  
 | 
| +part 'json_info_codec.dart';
 | 
| +
 | 
|  /// Common interface to many pieces of information generated by the dart2js
 | 
|  /// compiler that are directly associated with an element (compilation unit,
 | 
|  /// library, class, function, or field).
 | 
| @@ -35,12 +39,7 @@ abstract class Info {
 | 
|    /// Info of the enclosing element.
 | 
|    Info parent;
 | 
|  
 | 
| -  /// Serializes the information into a JSON format.
 | 
| -  // TODO(sigmund): refactor and put toJson outside the class, so we can have 2
 | 
| -  // different serializer/deserializers at once.
 | 
| -  Map toJson();
 | 
| -
 | 
| -  void accept(InfoVisitor visitor);
 | 
| +  dynamic accept(InfoVisitor visitor);
 | 
|  }
 | 
|  
 | 
|  /// Common information used for most kind of elements.
 | 
| @@ -68,21 +67,6 @@ abstract class BasicInfo implements Info {
 | 
|        : kind = _kindFromSerializedId(serializedId),
 | 
|          id = _idFromSerializedId(serializedId);
 | 
|  
 | 
| -  Map toJson() {
 | 
| -    var res = {
 | 
| -      'id': serializedId,
 | 
| -      'kind': _kindToString(kind),
 | 
| -      'name': name,
 | 
| -      'size': size,
 | 
| -    };
 | 
| -    // TODO(sigmund): Omit this also when outputUnit.id == 0 (most code is in
 | 
| -    // the main output unit by default).
 | 
| -    if (outputUnit != null) res['outputUnit'] = outputUnit.serializedId;
 | 
| -    if (coverageId != null) res['coverageId'] = coverageId;
 | 
| -    if (parent != null) res['parent'] = parent.serializedId;
 | 
| -    return res;
 | 
| -  }
 | 
| -
 | 
|    String toString() => '$serializedId $name [$size]';
 | 
|  }
 | 
|  
 | 
| @@ -150,57 +134,7 @@ class AllInfo {
 | 
|  
 | 
|    AllInfo();
 | 
|  
 | 
| -  // TODO(het): Remove this when we have an external InfoCodec, see
 | 
| -  // https://github.com/dart-lang/dart2js_info/issues/4
 | 
| -  factory AllInfo.fromJson(Map json) => new _ParseHelper().parseAll(json);
 | 
| -
 | 
| -  Map _listAsJsonMap(List<Info> list) {
 | 
| -    var map = <String, Map>{};
 | 
| -    for (var info in list) {
 | 
| -      map['${info.id}'] = info.toJson();
 | 
| -    }
 | 
| -    return map;
 | 
| -  }
 | 
| -
 | 
| -  Map _extractHoldingInfo() {
 | 
| -    var map = <String, List>{};
 | 
| -    void helper(CodeInfo info) {
 | 
| -      if (info.uses.isEmpty) return;
 | 
| -      map[info.serializedId] = info.uses.map((u) => u.toJson()).toList();
 | 
| -    }
 | 
| -    functions.forEach(helper);
 | 
| -    fields.forEach(helper);
 | 
| -    return map;
 | 
| -  }
 | 
| -
 | 
| -  Map _extractDependencies() {
 | 
| -    var map = <String, List>{};
 | 
| -    dependencies.forEach((k, v) {
 | 
| -      map[k.serializedId] = v.map((i) => i.serializedId).toList();
 | 
| -    });
 | 
| -    return map;
 | 
| -  }
 | 
| -
 | 
| -  Map toJson() => {
 | 
| -        'elements': {
 | 
| -          'library': _listAsJsonMap(libraries),
 | 
| -          'class': _listAsJsonMap(classes),
 | 
| -          'function': _listAsJsonMap(functions),
 | 
| -          'typedef': _listAsJsonMap(typedefs),
 | 
| -          'field': _listAsJsonMap(fields),
 | 
| -          'constant': _listAsJsonMap(constants),
 | 
| -        },
 | 
| -        'holding': _extractHoldingInfo(),
 | 
| -        'dependencies': _extractDependencies(),
 | 
| -        'outputUnits': outputUnits.map((u) => u.toJson()).toList(),
 | 
| -        'dump_version': version,
 | 
| -        'deferredFiles': deferredFiles,
 | 
| -        'dump_minor_version': '$minorVersion',
 | 
| -        // TODO(sigmund): change viewer to accept an int?
 | 
| -        'program': program.toJson(),
 | 
| -      };
 | 
| -
 | 
| -  void accept(InfoVisitor visitor) => visitor.visitAll(this);
 | 
| +  dynamic accept(InfoVisitor visitor) => visitor.visitAll(this);
 | 
|  }
 | 
|  
 | 
|  class ProgramInfo {
 | 
| @@ -226,216 +160,7 @@ class ProgramInfo {
 | 
|        this.noSuchMethodEnabled,
 | 
|        this.minified});
 | 
|  
 | 
| -  Map toJson() => {
 | 
| -        'entrypoint': entrypoint.serializedId,
 | 
| -        'size': size,
 | 
| -        'dart2jsVersion': dart2jsVersion,
 | 
| -        'compilationMoment': '$compilationMoment',
 | 
| -        'compilationDuration': '${compilationDuration}',
 | 
| -        'toJsonDuration': toJsonDuration,
 | 
| -        'dumpInfoDuration': '$dumpInfoDuration',
 | 
| -        'noSuchMethodEnabled': noSuchMethodEnabled,
 | 
| -        'minified': minified,
 | 
| -      };
 | 
| -
 | 
| -  void accept(InfoVisitor visitor) => visitor.visitProgram(this);
 | 
| -}
 | 
| -
 | 
| -// TODO(sigmund): add unit tests.
 | 
| -class _ParseHelper {
 | 
| -  Map<String, Info> registry = {};
 | 
| -
 | 
| -  AllInfo parseAll(Map json) {
 | 
| -    var result = new AllInfo();
 | 
| -    var elements = json['elements'];
 | 
| -    result.libraries.addAll(elements['library'].values.map(parseLibrary));
 | 
| -    result.classes.addAll(elements['class'].values.map(parseClass));
 | 
| -    result.functions.addAll(elements['function'].values.map(parseFunction));
 | 
| -    result.fields.addAll(elements['field'].values.map(parseField));
 | 
| -    result.typedefs.addAll(elements['typedef'].values.map(parseTypedef));
 | 
| -
 | 
| -    // TODO(sigmund): remove null check on next breaking version
 | 
| -    var constants = elements['constant'];
 | 
| -    if (constants != null) {
 | 
| -      result.constants.addAll(constants.values.map(parseConstant));
 | 
| -    }
 | 
| -
 | 
| -    var idMap = {};
 | 
| -    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];
 | 
| -      assert(src != null);
 | 
| -      for (var dep in deps) {
 | 
| -        var target = idMap[dep['id']];
 | 
| -        assert(target != null);
 | 
| -        src.uses.add(new DependencyInfo(target, dep['mask']));
 | 
| -      }
 | 
| -    });
 | 
| -
 | 
| -    json['dependencies']?.forEach((k, deps) {
 | 
| -      result.dependencies[idMap[k]] = deps.map((d) => idMap[d]).toList();
 | 
| -    });
 | 
| -
 | 
| -    result.program = parseProgram(json['program']);
 | 
| -    // todo: version, etc
 | 
| -    return result;
 | 
| -  }
 | 
| -
 | 
| -  LibraryInfo parseLibrary(Map json) {
 | 
| -    LibraryInfo result = parseId(json['id']);
 | 
| -    result
 | 
| -      ..name = json['name']
 | 
| -      ..uri = Uri.parse(json['canonicalUri'])
 | 
| -      ..outputUnit = parseId(json['outputUnit'])
 | 
| -      ..size = json['size'];
 | 
| -    for (var child in json['children'].map(parseId)) {
 | 
| -      if (child is FunctionInfo) {
 | 
| -        result.topLevelFunctions.add(child);
 | 
| -      } else if (child is FieldInfo) {
 | 
| -        result.topLevelVariables.add(child);
 | 
| -      } else if (child is ClassInfo) {
 | 
| -        result.classes.add(child);
 | 
| -      } else {
 | 
| -        assert(child is TypedefInfo);
 | 
| -        result.typedefs.add(child);
 | 
| -      }
 | 
| -    }
 | 
| -    return result;
 | 
| -  }
 | 
| -
 | 
| -  ClassInfo parseClass(Map json) {
 | 
| -    ClassInfo result = parseId(json['id']);
 | 
| -    result
 | 
| -      ..name = json['name']
 | 
| -      ..parent = parseId(json['parent'])
 | 
| -      ..outputUnit = parseId(json['outputUnit'])
 | 
| -      ..size = json['size']
 | 
| -      ..isAbstract = json['modifiers']['abstract'] == true;
 | 
| -    assert(result is ClassInfo);
 | 
| -    for (var child in json['children'].map(parseId)) {
 | 
| -      if (child is FunctionInfo) {
 | 
| -        result.functions.add(child);
 | 
| -      } else {
 | 
| -        assert(child is FieldInfo);
 | 
| -        result.fields.add(child);
 | 
| -      }
 | 
| -    }
 | 
| -    return result;
 | 
| -  }
 | 
| -
 | 
| -  FieldInfo parseField(Map json) {
 | 
| -    FieldInfo result = parseId(json['id']);
 | 
| -    return result
 | 
| -      ..name = json['name']
 | 
| -      ..parent = parseId(json['parent'])
 | 
| -      ..coverageId = json['coverageId']
 | 
| -      ..outputUnit = parseId(json['outputUnit'])
 | 
| -      ..size = json['size']
 | 
| -      ..type = json['type']
 | 
| -      ..inferredType = json['inferredType']
 | 
| -      ..code = json['code']
 | 
| -      ..isConst = json['const'] ?? false
 | 
| -      ..initializer = parseId(json['initializer'])
 | 
| -      ..closures = json['children'].map(parseId).toList();
 | 
| -  }
 | 
| -
 | 
| -  ConstantInfo parseConstant(Map json) {
 | 
| -    ConstantInfo result = parseId(json['id']);
 | 
| -    return result
 | 
| -      ..code = json['code']
 | 
| -      ..size = json['size'];
 | 
| -  }
 | 
| -
 | 
| -  TypedefInfo parseTypedef(Map json) {
 | 
| -    TypedefInfo result = parseId(json['id']);
 | 
| -    return result
 | 
| -      ..name = json['name']
 | 
| -      ..parent = parseId(json['parent'])
 | 
| -      ..type = json['type']
 | 
| -      ..size = 0;
 | 
| -  }
 | 
| -
 | 
| -  ProgramInfo parseProgram(Map json) => new ProgramInfo()
 | 
| -    ..size = json['size']
 | 
| -    ..entrypoint = parseId(json['entrypoint']);
 | 
| -
 | 
| -  FunctionInfo parseFunction(Map json) {
 | 
| -    FunctionInfo result = parseId(json['id']);
 | 
| -    return result
 | 
| -      ..name = json['name']
 | 
| -      ..parent = parseId(json['parent'])
 | 
| -      ..coverageId = json['coverageId']
 | 
| -      ..outputUnit = parseId(json['outputUnit'])
 | 
| -      ..size = json['size']
 | 
| -      ..type = json['type']
 | 
| -      ..returnType = json['returnType']
 | 
| -      ..inferredReturnType = json['inferredReturnType']
 | 
| -      ..parameters = json['parameters'].map(parseParameter).toList()
 | 
| -      ..code = json['code']
 | 
| -      ..sideEffects = json['sideEffects']
 | 
| -      ..modifiers = parseModifiers(json['modifiers'])
 | 
| -      ..closures = json['children'].map(parseId).toList()
 | 
| -      ..measurements = parseMeasurements(json['measurements']);
 | 
| -  }
 | 
| -
 | 
| -  ParameterInfo parseParameter(Map json) =>
 | 
| -      new ParameterInfo(json['name'], json['type'], json['declaredType']);
 | 
| -
 | 
| -  Measurements parseMeasurements(Map json) {
 | 
| -    if (json == null) return null;
 | 
| -    var uri = json['sourceFile'];
 | 
| -    var res = new Measurements(uri == null ? null : Uri.parse(uri));
 | 
| -    for (var key in json.keys) {
 | 
| -      var value = json[key];
 | 
| -      if (value == null) continue;
 | 
| -      if (key == 'entries') {
 | 
| -        value.forEach((metricName, entries) {
 | 
| -          var metric = Metric.fromJson(metricName);
 | 
| -          for (var i = 0; i < entries.length; i += 2) {
 | 
| -            res.record(metric, entries[i], entries[i + 1]);
 | 
| -          }
 | 
| -        });
 | 
| -      } else {
 | 
| -        res.counters[Metric.fromJson(key)] = value;
 | 
| -      }
 | 
| -    }
 | 
| -    return res;
 | 
| -  }
 | 
| -
 | 
| -  FunctionModifiers parseModifiers(Map<String, bool> json) {
 | 
| -    return new FunctionModifiers(
 | 
| -        isStatic: json['static'] == true,
 | 
| -        isConst: json['const'] == true,
 | 
| -        isFactory: json['factory'] == true,
 | 
| -        isExternal: json['external'] == true);
 | 
| -  }
 | 
| -
 | 
| -  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('library/')) {
 | 
| -          return new LibraryInfo._(serializedId);
 | 
| -        } else if (serializedId.startsWith('class/')) {
 | 
| -          return new ClassInfo._(serializedId);
 | 
| -        } else if (serializedId.startsWith('field/')) {
 | 
| -          return new FieldInfo._(serializedId);
 | 
| -        } else if (serializedId.startsWith('constant/')) {
 | 
| -          return new ConstantInfo._(serializedId);
 | 
| -        } else if (serializedId.startsWith('typedef/')) {
 | 
| -          return new TypedefInfo._(serializedId);
 | 
| -        } else if (serializedId.startsWith('outputUnit/')) {
 | 
| -          return new OutputUnitInfo._(serializedId);
 | 
| -        }
 | 
| -        assert(false);
 | 
| -      });
 | 
| +  dynamic accept(InfoVisitor visitor) => visitor.visitProgram(this);
 | 
|  }
 | 
|  
 | 
|  /// Info associated with a library element.
 | 
| @@ -470,17 +195,7 @@ class LibraryInfo extends BasicInfo {
 | 
|  
 | 
|    LibraryInfo._(String serializedId) : super._fromId(serializedId);
 | 
|  
 | 
| -  Map toJson() => super.toJson()
 | 
| -    ..addAll({
 | 
| -      'children': []
 | 
| -        ..addAll(topLevelFunctions.map((f) => f.serializedId))
 | 
| -        ..addAll(topLevelVariables.map((v) => v.serializedId))
 | 
| -        ..addAll(classes.map((c) => c.serializedId))
 | 
| -        ..addAll(typedefs.map((t) => t.serializedId)),
 | 
| -      'canonicalUri': '$uri',
 | 
| -    });
 | 
| -
 | 
| -  void accept(InfoVisitor visitor) => visitor.visitLibrary(this);
 | 
| +  dynamic accept(InfoVisitor visitor) => visitor.visitLibrary(this);
 | 
|  }
 | 
|  
 | 
|  /// Information about an output unit. Normally there is just one for the entire
 | 
| @@ -493,7 +208,7 @@ class OutputUnitInfo extends BasicInfo {
 | 
|  
 | 
|    OutputUnitInfo._(String serializedId) : super._fromId(serializedId);
 | 
|  
 | 
| -  void accept(InfoVisitor visitor) => visitor.visitOutput(this);
 | 
| +  dynamic accept(InfoVisitor visitor) => visitor.visitOutput(this);
 | 
|  }
 | 
|  
 | 
|  /// Information about a class element.
 | 
| @@ -517,16 +232,7 @@ class ClassInfo extends BasicInfo {
 | 
|  
 | 
|    ClassInfo._(String serializedId) : super._fromId(serializedId);
 | 
|  
 | 
| -  Map toJson() => super.toJson()
 | 
| -    ..addAll({
 | 
| -      // TODO(sigmund): change format, include only when abstract is true.
 | 
| -      'modifiers': {'abstract': isAbstract},
 | 
| -      'children': []
 | 
| -        ..addAll(fields.map((f) => f.serializedId))
 | 
| -        ..addAll(functions.map((m) => m.serializedId))
 | 
| -    });
 | 
| -
 | 
| -  void accept(InfoVisitor visitor) => visitor.visitClass(this);
 | 
| +  dynamic accept(InfoVisitor visitor) => visitor.visitClass(this);
 | 
|  }
 | 
|  
 | 
|  /// Information about a constant value.
 | 
| @@ -542,9 +248,7 @@ class ConstantInfo extends BasicInfo {
 | 
|  
 | 
|    ConstantInfo._(String serializedId) : super._fromId(serializedId);
 | 
|  
 | 
| -  Map toJson() => super.toJson()..addAll({'code': code});
 | 
| -
 | 
| -  void accept(InfoVisitor visitor) => visitor.visitConstant(this);
 | 
| +  dynamic accept(InfoVisitor visitor) => visitor.visitConstant(this);
 | 
|  }
 | 
|  
 | 
|  /// Information about a field element.
 | 
| @@ -582,22 +286,7 @@ class FieldInfo extends BasicInfo with CodeInfo {
 | 
|  
 | 
|    FieldInfo._(String serializedId) : super._fromId(serializedId);
 | 
|  
 | 
| -  Map toJson() {
 | 
| -    var result = super.toJson()
 | 
| -      ..addAll({
 | 
| -        'children': closures.map((i) => i.serializedId).toList(),
 | 
| -        'inferredType': inferredType,
 | 
| -        'code': code,
 | 
| -        'type': type,
 | 
| -      });
 | 
| -    if (isConst) {
 | 
| -      result['const'] = true;
 | 
| -      if (initializer != null) result['initializer'] = initializer.serializedId;
 | 
| -    }
 | 
| -    return result;
 | 
| -  }
 | 
| -
 | 
| -  void accept(InfoVisitor visitor) => visitor.visitField(this);
 | 
| +  dynamic accept(InfoVisitor visitor) => visitor.visitField(this);
 | 
|  }
 | 
|  
 | 
|  /// Information about a typedef declaration.
 | 
| @@ -611,9 +300,7 @@ class TypedefInfo extends BasicInfo {
 | 
|  
 | 
|    TypedefInfo._(String serializedId) : super._fromId(serializedId);
 | 
|  
 | 
| -  Map toJson() => super.toJson()..['type'] = '$type';
 | 
| -
 | 
| -  void accept(InfoVisitor visitor) => visitor.visitTypedef(this);
 | 
| +  dynamic accept(InfoVisitor visitor) => visitor.visitTypedef(this);
 | 
|  }
 | 
|  
 | 
|  /// Information about a function or method.
 | 
| @@ -678,23 +365,7 @@ class FunctionInfo extends BasicInfo with CodeInfo {
 | 
|  
 | 
|    FunctionInfo._(String serializedId) : super._fromId(serializedId);
 | 
|  
 | 
| -  Map toJson() => super.toJson()
 | 
| -    ..addAll({
 | 
| -      'children': closures.map((i) => i.serializedId).toList(),
 | 
| -      'modifiers': modifiers.toJson(),
 | 
| -      'returnType': returnType,
 | 
| -      'inferredReturnType': inferredReturnType,
 | 
| -      'parameters': parameters.map((p) => p.toJson()).toList(),
 | 
| -      'sideEffects': sideEffects,
 | 
| -      'inlinedCount': inlinedCount,
 | 
| -      'code': code,
 | 
| -      'type': type,
 | 
| -      'measurements': measurements?.toJson(),
 | 
| -      // Note: version 3.2 of dump-info serializes `uses` in a section called
 | 
| -      // `holding` at the top-level.
 | 
| -    });
 | 
| -
 | 
| -  void accept(InfoVisitor visitor) => visitor.visitFunction(this);
 | 
| +  dynamic accept(InfoVisitor visitor) => visitor.visitFunction(this);
 | 
|  }
 | 
|  
 | 
|  /// Information about how a dependency is used.
 | 
| @@ -708,8 +379,6 @@ class DependencyInfo {
 | 
|    final String mask;
 | 
|  
 | 
|    DependencyInfo(this.target, this.mask);
 | 
| -
 | 
| -  Map toJson() => {'id': target.serializedId, 'mask': mask};
 | 
|  }
 | 
|  
 | 
|  /// Name and type information about a function parameter.
 | 
| @@ -719,8 +388,6 @@ class ParameterInfo {
 | 
|    final String declaredType;
 | 
|  
 | 
|    ParameterInfo(this.name, this.type, this.declaredType);
 | 
| -
 | 
| -  Map toJson() => {'name': name, 'type': type, 'declaredType': declaredType};
 | 
|  }
 | 
|  
 | 
|  /// Modifiers that may apply to methods.
 | 
| @@ -735,22 +402,6 @@ class FunctionModifiers {
 | 
|        this.isConst: false,
 | 
|        this.isFactory: false,
 | 
|        this.isExternal: false});
 | 
| -
 | 
| -  // TODO(sigmund): exclude false values (requires bumping the format version):
 | 
| -  //   Map toJson() {
 | 
| -  //     var res = <String, bool>{};
 | 
| -  //     if (isStatic) res['static'] = true;
 | 
| -  //     if (isConst) res['const'] = true;
 | 
| -  //     if (isFactory) res['factory'] = true;
 | 
| -  //     if (isExternal) res['external'] = true;
 | 
| -  //     return res;
 | 
| -  //   }
 | 
| -  Map toJson() => {
 | 
| -        'static': isStatic,
 | 
| -        'const': isConst,
 | 
| -        'factory': isFactory,
 | 
| -        'external': isExternal,
 | 
| -      };
 | 
|  }
 | 
|  
 | 
|  /// Possible values of the `kind` field in the serialied infos.
 | 
| @@ -813,16 +464,16 @@ InfoKind _kindFromString(String kind) {
 | 
|  }
 | 
|  
 | 
|  /// A simple visitor for information produced by the dart2js compiler.
 | 
| -class InfoVisitor {
 | 
| -  visitAll(AllInfo info) {}
 | 
| -  visitProgram(ProgramInfo info) {}
 | 
| -  visitLibrary(LibraryInfo info) {}
 | 
| -  visitClass(ClassInfo info) {}
 | 
| -  visitField(FieldInfo info) {}
 | 
| -  visitConstant(ConstantInfo info) {}
 | 
| -  visitFunction(FunctionInfo info) {}
 | 
| -  visitTypedef(TypedefInfo info) {}
 | 
| -  visitOutput(OutputUnitInfo info) {}
 | 
| +abstract class InfoVisitor<T> {
 | 
| +  T visitAll(AllInfo info);
 | 
| +  T visitProgram(ProgramInfo info);
 | 
| +  T visitLibrary(LibraryInfo info);
 | 
| +  T visitClass(ClassInfo info);
 | 
| +  T visitField(FieldInfo info);
 | 
| +  T visitConstant(ConstantInfo info);
 | 
| +  T visitFunction(FunctionInfo info);
 | 
| +  T visitTypedef(TypedefInfo info);
 | 
| +  T visitOutput(OutputUnitInfo info);
 | 
|  }
 | 
|  
 | 
|  /// A visitor that recursively walks each portion of the program. Because the
 | 
| @@ -831,7 +482,7 @@ class InfoVisitor {
 | 
|  /// visitAll contains references to functions, this visitor only recurses to
 | 
|  /// visit libraries, then from each library we visit functions and classes, and
 | 
|  /// so on.
 | 
| -class RecursiveInfoVisitor extends InfoVisitor {
 | 
| +class RecursiveInfoVisitor extends InfoVisitor<Null> {
 | 
|    visitAll(AllInfo info) {
 | 
|      // Note: we don't visit functions, fields, classes, and typedefs because
 | 
|      // they are reachable from the library info.
 | 
| @@ -839,6 +490,8 @@ class RecursiveInfoVisitor extends InfoVisitor {
 | 
|      info.constants.forEach(visitConstant);
 | 
|    }
 | 
|  
 | 
| +  visitProgram(ProgramInfo info) {}
 | 
| +
 | 
|    visitLibrary(LibraryInfo info) {
 | 
|      info.topLevelFunctions.forEach(visitFunction);
 | 
|      info.topLevelVariables.forEach(visitField);
 | 
| @@ -855,7 +508,12 @@ class RecursiveInfoVisitor extends InfoVisitor {
 | 
|      info.closures.forEach(visitFunction);
 | 
|    }
 | 
|  
 | 
| +  visitConstant(ConstantInfo info) {}
 | 
| +
 | 
|    visitFunction(FunctionInfo info) {
 | 
|      info.closures.forEach(visitFunction);
 | 
|    }
 | 
| +
 | 
| +  visitTypedef(TypedefInfo info) {}
 | 
| +  visitOutput(OutputUnitInfo info) {}
 | 
|  }
 | 
| 
 |