[dart2js] separate main and deferred units in programInfo tree structures

Change-Id: Ia1a6ba772fa6384ef23ab5d814a0228c7d5613bc
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/252870
Commit-Queue: Islina Shan <islinashan@google.com>
Reviewed-by: Mark Zhou <markzipan@google.com>
diff --git a/pkg/dart2js_info/bin/src/to_devtools_format.dart b/pkg/dart2js_info/bin/src/to_devtools_format.dart
index 8e12f5d..9469c95 100644
--- a/pkg/dart2js_info/bin/src/to_devtools_format.dart
+++ b/pkg/dart2js_info/bin/src/to_devtools_format.dart
@@ -1,13 +1,13 @@
+library dart2js_info.bin.to_devtools_format;
+
 import 'dart:convert';
 import 'dart:io';
 
 import 'package:args/command_runner.dart';
 import 'package:dart2js_info/info.dart';
 import 'package:dart2js_info/src/io.dart';
-import 'package:dart2js_info/src/util.dart';
+import 'package:dart2js_info/src/util.dart' show longName, libraryGroupName;
 import 'package:vm_snapshot_analysis/program_info.dart' as vm;
-import 'package:vm_snapshot_analysis/treemap.dart';
-
 import 'usage_exception.dart';
 
 /// Command that converts a `--dump-info` JSON output into a format ingested by Devtools.
@@ -39,12 +39,12 @@
     final AllInfo allInfo = await infoFromFile(args.rest.first);
     final builder = ProgramInfoBuilder(allInfo);
     vm.ProgramInfo programInfo = builder.build(allInfo);
-    Map<String, dynamic> treeMap = treemapFromInfo(programInfo);
-    var treeMapJson = jsonEncode(treeMap);
+    Map<String, dynamic> programInfoTree = programInfo.toJson();
+    // TODO: convert the programInfo tree to a treemap
     if (outputPath == null) {
-      print(treeMapJson);
+      print(jsonEncode(programInfoTree));
     } else {
-      await File(outputPath).writeAsString(treeMapJson);
+      await File(outputPath).writeAsString(jsonEncode(programInfoTree));
     }
   }
 }
@@ -61,17 +61,34 @@
 
   final List<vm.ProgramInfoNode> outputInfo = [];
 
+  /// Mapping between the filename of the outputUnit and the [vm.ProgramInfo]
+  /// subtree representing a program unit (main or deferred).
+  final Map<String, vm.ProgramInfo> outputUnits = {};
+
+  /// Mapping between the name of the library [vm.ProgramInfoNode]
+  /// object and its corresponding outputUnit [vm.ProgramInfo] tree.
+  final Map<String, vm.ProgramInfo> libraryUnits = {};
+
+  /// Mapping between the name of an [Info] object and the corresponding
+  /// [vm.ProgramInfoNode] object.
+  ///
+  /// For packages and libraries, since their children can be split among
+  /// different outputUnits, a composite name is used instead to differentiate
+  /// between [vm.ProgramInfoNode] in different outputUnits.
+  final Map<String, vm.ProgramInfoNode> infoNodesByName = {};
+
+  /// A unique key composed of the name of an [Info] object and the
+  /// filename of the outputUnit.
+  String compositeName(String name, String outputUnitName) =>
+      "$name/$outputUnitName";
+
   /// Mapping between the name of a [Info] object and the corresponding
   /// [vm.ProgramInfoNode] object.
-  Map<String, vm.ProgramInfoNode> infoNodesByName = {};
-
-  /// Mapping between the id (aka coverageId) of an [AllInfo] node and the
-  /// corresponding [vm.ProgramInfoNode] object.
   final Map<String, vm.ProgramInfoNode> infoNodesById = {};
 
-  /// Mapping between package names and the corresponding [vm.ProgramInfoNode]
-  /// objects of [vm.NodeType.packageNode].
-  final Map<String, vm.ProgramInfoNode> packageInfoNodes = {};
+  /// Mapping between the composite name of a package and the corresponding
+  /// [vm.ProgramInfoNode] objects of [vm.NodeType.packageNode].
+  final Map<String, dynamic> packageInfoNodes = {};
 
   /// Mapping between an <unnamed> [LibraryInfo] object and the name of the
   /// corresponding [vm.ProgramInfoNode] object.
@@ -79,33 +96,289 @@
 
   ProgramInfoBuilder(this.info);
 
+  /// Collect libraries into packages and aggregate their sizes.
+  void makePackage(LibraryInfo libraryInfo, String outputUnitName) {
+    vm.ProgramInfo outputUnit = outputUnits[outputUnitName]!;
+    String libraryName = libraryInfo.name;
+    if (libraryInfo.name == '<unnamed>') {
+      libraryName = longName(libraryInfo, useLibraryUri: true, forId: true);
+    }
+    String packageName = libraryGroupName(libraryInfo) ?? libraryName;
+    vm.ProgramInfoNode? packageInfoNode = packageInfoNodes[packageName];
+    if (packageInfoNode == null) {
+      String compositePackageName = compositeName(packageName, outputUnitName);
+      vm.ProgramInfoNode newPackage = outputUnit.makeNode(
+          name: compositePackageName,
+          parent: outputUnit.root,
+          type: vm.NodeType.packageNode);
+      newPackage.size = libraryInfo.size;
+      packageInfoNodes[compositePackageName] = newPackage;
+      outputUnit.root.children[compositePackageName] = newPackage;
+      outputInfo.add(newPackage);
+      var packageNode = infoNodesByName[compositePackageName];
+      assert(packageNode == null,
+          "encountered package with duplicated name: $compositePackageName");
+      infoNodesByName[compositePackageName] = newPackage;
+    } else {
+      packageInfoNode.size = (packageInfoNode.size ?? 0) + libraryInfo.size;
+    }
+  }
+
+  void makeLibrary(LibraryInfo libraryInfo, String outputUnitName) {
+    vm.ProgramInfo outputUnit = outputUnits[outputUnitName]!;
+    String libraryName = libraryInfo.name;
+    if (libraryName == '<unnamed>') {
+      libraryName = longName(libraryInfo, useLibraryUri: true, forId: true);
+      unnamedLibraries[libraryInfo] = libraryName;
+    }
+    String packageName = libraryGroupName(libraryInfo) ?? libraryName;
+    String compositePackageName = compositeName(packageName, outputUnitName);
+    vm.ProgramInfoNode parentNode = infoNodesByName[compositePackageName]!;
+    String compositeLibraryName = compositeName(libraryName, outputUnitName);
+    vm.ProgramInfoNode newLibrary = outputUnit.makeNode(
+        name: compositeLibraryName,
+        parent: parentNode,
+        type: vm.NodeType.libraryNode);
+    newLibrary.size = libraryInfo.size;
+    parentNode.children[newLibrary.name] = newLibrary;
+    vm.ProgramInfoNode? libraryNode = infoNodesByName[compositeLibraryName];
+    assert(libraryNode == null,
+        "encountered library with duplicated name: $compositeLibraryName");
+    infoNodesByName[compositeLibraryName] = newLibrary;
+    outputInfo.add(newLibrary);
+  }
+
+  void makeFunction(FunctionInfo functionInfo) {
+    Info? parent = functionInfo.parent;
+    String outputUnitName = functionInfo.outputUnit!.filename;
+    vm.ProgramInfo? outputUnit = outputUnits[outputUnitName];
+    if (parent != null && outputUnit != null) {
+      assert(parent.kind == kindFromString('library'));
+      vm.ProgramInfoNode parentNode;
+      if (parent.kind == kindFromString('library')) {
+        if (parent.name == "<unnamed>") {
+          var tempName =
+              compositeName(unnamedLibraries[parent]!, outputUnitName);
+          parentNode = infoNodesByName[tempName]!;
+        } else {
+          parentNode =
+              infoNodesByName[compositeName(parent.name, outputUnitName)]!;
+        }
+      } else {
+        parentNode = infoNodesByName[parent.name]!;
+      }
+      vm.ProgramInfoNode newFunction = outputUnit.makeNode(
+          name: functionInfo.name,
+          parent: parentNode,
+          type: vm.NodeType.functionNode);
+      newFunction.size = functionInfo.size;
+      parentNode.children[newFunction.name] = newFunction;
+      vm.ProgramInfoNode? functionNode = infoNodesByName[newFunction.name];
+      assert(functionNode == null,
+          "encountered function with duplicated name: $newFunction.name");
+      infoNodesByName[newFunction.name] = newFunction;
+      outputInfo.add(newFunction);
+    }
+  }
+
+  void makeClass(ClassInfo classInfo) {
+    Info? parent = classInfo.parent;
+    String outputUnitName = classInfo.outputUnit!.filename;
+    vm.ProgramInfo? outputUnit = outputUnits[outputUnitName];
+    if (parent != null && outputUnit != null) {
+      vm.ProgramInfoNode parentNode;
+      if (parent.kind == kindFromString('library')) {
+        if (parent.name == "<unnamed>") {
+          var tempName =
+              compositeName(unnamedLibraries[parent]!, outputUnitName);
+          parentNode = infoNodesByName[tempName]!;
+        } else {
+          parentNode =
+              infoNodesByName[compositeName(parent.name, outputUnitName)]!;
+        }
+      } else {
+        parentNode = infoNodesByName[parent.name]!;
+      }
+      vm.ProgramInfoNode newClass = outputUnit.makeNode(
+          name: classInfo.name,
+          parent: parentNode,
+          type: vm.NodeType.classNode);
+      newClass.size = classInfo.size;
+      parentNode.children[newClass.name] = newClass;
+      vm.ProgramInfoNode? classNode = infoNodesByName[newClass.name];
+      assert(classNode == null,
+          "encountered class with duplicated name: $newClass.name");
+      infoNodesByName[newClass.name] = newClass;
+      outputInfo.add(newClass);
+    }
+  }
+
+  /// Fields are currently assigned [vm.NodeType.other].
+  ///
+  /// Note: we might want to create a separate [vm.NodeType.fieldNode] to
+  /// differentiate fields from other miscellaneous nodes for constructing
+  /// the call graph.
+  void makeField(FieldInfo fieldInfo) {
+    Info? parent = fieldInfo.parent;
+    String outputUnitName = fieldInfo.outputUnit!.filename;
+    vm.ProgramInfo? outputUnit = outputUnits[outputUnitName];
+    if (parent != null && outputUnit != null) {
+      vm.ProgramInfoNode parentNode;
+      if (parent.kind == kindFromString('library')) {
+        if (parent.name == "<unnamed>") {
+          var tempName =
+              compositeName(unnamedLibraries[parent]!, outputUnitName);
+          parentNode = infoNodesByName[tempName]!;
+        } else {
+          parentNode =
+              infoNodesByName[compositeName(parent.name, outputUnitName)]!;
+        }
+      } else {
+        parentNode = infoNodesByName[parent.name]!;
+      }
+      vm.ProgramInfoNode newField = outputUnit.makeNode(
+          name: fieldInfo.name, parent: parentNode, type: vm.NodeType.other);
+      newField.size = fieldInfo.size;
+      parentNode.children[newField.name] = newField;
+      vm.ProgramInfoNode? fieldNode = infoNodesByName[newField.name];
+      assert(fieldNode == null,
+          "encountered field with duplicated name: $newField.name");
+      infoNodesByName[newField.name] = newField;
+      outputInfo.add(newField);
+    }
+  }
+
+  void makeConstant(ConstantInfo constantInfo) {
+    String? constantName = constantInfo.code.first.text ??
+        "${constantInfo.code.first.start}/${constantInfo.code.first.end}";
+    String outputUnitName = constantInfo.outputUnit!.filename;
+    vm.ProgramInfo? outputUnit = outputUnits[outputUnitName];
+    vm.ProgramInfoNode newConstant = outputUnit!.makeNode(
+        name: constantName, parent: outputUnit.root, type: vm.NodeType.other);
+    newConstant.size = constantInfo.size;
+    outputUnit.root.children[newConstant.name] = newConstant;
+    vm.ProgramInfoNode? constantNode = infoNodesByName[newConstant.name];
+    assert(constantNode == null,
+        "encountered constant with duplicated name: $newConstant.name");
+    infoNodesByName[newConstant.name] = newConstant;
+    outputInfo.add(newConstant);
+  }
+
+  void makeTypedef(TypedefInfo typedefInfo) {
+    String outputUnitName = typedefInfo.outputUnit!.filename;
+    vm.ProgramInfo? outputUnit = outputUnits[outputUnitName];
+    vm.ProgramInfoNode newTypedef = outputUnit!.makeNode(
+        name: typedefInfo.name,
+        parent: outputUnit.root,
+        type: vm.NodeType.other);
+    newTypedef.size = typedefInfo.size;
+    vm.ProgramInfoNode? typedefNode = infoNodesByName[newTypedef.name];
+    assert(typedefNode == null,
+        "encountered constant with duplicated name: $newTypedef.name");
+    outputInfo.add(newTypedef);
+  }
+
+  void makeClassType(ClassTypeInfo classTypeInfo) {
+    Info? parent = classTypeInfo.parent;
+    String outputUnitName = classTypeInfo.outputUnit!.filename;
+    vm.ProgramInfo? outputUnit = outputUnits[outputUnitName];
+    if (parent != null && outputUnit != null) {
+      assert(parent.kind == kindFromString('library'));
+      vm.ProgramInfoNode parentNode;
+      if (parent.name == "<unnamed>") {
+        var tempName = compositeName(unnamedLibraries[parent]!, outputUnitName);
+        parentNode = infoNodesByName[tempName]!;
+      } else {
+        parentNode =
+            infoNodesByName[compositeName(parent.name, outputUnitName)]!;
+      }
+      vm.ProgramInfoNode newClassType = outputUnit.makeNode(
+          name: classTypeInfo.name,
+          parent: parentNode,
+          type: vm.NodeType.other);
+      newClassType.size = classTypeInfo.size;
+      vm.ProgramInfoNode? classTypeNode = infoNodesByName[newClassType.name];
+      assert(classTypeNode == null,
+          "encountered classType with duplicated name: $newClassType.name");
+      infoNodesByName[newClassType.name] = newClassType;
+      outputInfo.add(newClassType);
+    }
+  }
+
+  void makeClosure(ClosureInfo closureInfo) {
+    Info? parent = closureInfo.parent;
+    String outputUnitName = closureInfo.outputUnit!.filename;
+    vm.ProgramInfo? outputUnit = outputUnits[outputUnitName];
+    if (parent != null && outputUnit != null) {
+      vm.ProgramInfoNode parentNode;
+      if (parent.kind == kindFromString('library')) {
+        if (parent.name == "<unnamed>") {
+          var tempName =
+              compositeName(unnamedLibraries[parent]!, outputUnitName);
+          parentNode = infoNodesByName[tempName]!;
+        } else {
+          parentNode =
+              infoNodesByName[compositeName(parent.name, outputUnitName)]!;
+        }
+      } else {
+        parentNode = infoNodesByName[parent.name]!;
+      }
+      vm.ProgramInfoNode newClosure = outputUnit.makeNode(
+          name: closureInfo.name,
+          parent: parentNode,
+          // ProgramInfo trees consider closures and functions to both be of the functionNode type.
+          type: vm.NodeType.functionNode);
+      newClosure.size = closureInfo.size;
+      parentNode.children[newClosure.name] = newClosure;
+      vm.ProgramInfoNode? closureNode = infoNodesByName[newClosure.name];
+      assert(closureNode == null,
+          "encountered closure with duplicated name: $newClosure.name");
+      infoNodesByName[newClosure.name] = newClosure;
+      outputInfo.add(newClosure);
+    }
+  }
+
   @override
-  vm.ProgramInfoNode visitAll(AllInfo info) {
-    outputInfo.add(program.root);
-    info.libraries.forEach(makePackage);
-    info.packages.forEach(visitPackage);
-    info.libraries.forEach(makeLibrary);
-    info.libraries.forEach(visitLibrary);
+  vm.ProgramInfoNode visitAll(AllInfo info, String outputUnitName) {
+    outputInfo.add(outputUnits[outputUnitName]!.root);
+    visitProgram(info.program!);
+    for (var package in info.packages) {
+      visitPackage(package, outputUnitName);
+    }
+    for (var library in info.libraries) {
+      visitLibrary(library, outputUnitName);
+    }
     info.constants.forEach(makeConstant);
     info.constants.forEach(visitConstant);
-    return program.root;
+    return outputUnits[outputUnitName]!.root;
+  }
+
+  @override
+  vm.ProgramInfoNode visitOutput(OutputUnitInfo info) {
+    vm.ProgramInfo? outputUnit = outputUnits[info.filename];
+    assert(outputUnit == null, "encountered outputUnit with duplicated name");
+    var newUnit = vm.ProgramInfo();
+    outputUnits[info.filename] = newUnit;
+    outputUnits[info.filename]!.root.size = info.size;
+    return outputUnits[info.filename]!.root;
   }
 
   @override
   vm.ProgramInfoNode visitProgram(ProgramInfo info) {
-    throw Exception('Not supported for devtools format.');
+    program.root.size = info.size;
+    return program.root;
   }
 
   @override
-  vm.ProgramInfoNode visitPackage(PackageInfo info) {
-    info.libraries.forEach(makePackage);
-    info.libraries.forEach(makeLibrary);
-    info.libraries.forEach(visitLibrary);
-    return infoNodesByName[info.name]!;
+  vm.ProgramInfoNode visitPackage(PackageInfo info, String outputUnitName) {
+    for (var library in info.libraries) {
+      visitLibrary(library, outputUnitName);
+    }
+    return infoNodesByName[compositeName(info.name, outputUnitName)]!;
   }
 
   @override
-  vm.ProgramInfoNode visitLibrary(LibraryInfo info) {
+  vm.ProgramInfoNode visitLibrary(LibraryInfo info, String outputUnitName) {
     info.topLevelFunctions.forEach(makeFunction);
     info.topLevelFunctions.forEach(visitFunction);
     info.topLevelVariables.forEach(makeField);
@@ -116,8 +389,9 @@
     info.classTypes.forEach(visitClassType);
     info.typedefs.forEach(makeTypedef);
     info.typedefs.forEach(visitTypedef);
-    return infoNodesByName[info.name] ??
-        infoNodesByName[unnamedLibraries[info]]!;
+    return infoNodesByName[compositeName(info.name, outputUnitName)] ??
+        infoNodesByName[
+            compositeName(unnamedLibraries[info]!, outputUnitName)]!;
   }
 
   @override
@@ -157,7 +431,8 @@
 
   @override
   vm.ProgramInfoNode visitConstant(ConstantInfo info) {
-    return infoNodesByName[info.code.first.text!]!;
+    return infoNodesByName[info.code.first.text] ??
+        infoNodesByName["${info.code.first.start}/${info.code.first.end}"]!;
   }
 
   @override
@@ -165,191 +440,18 @@
     return infoNodesByName[info.name]!;
   }
 
-  @override
-  vm.ProgramInfoNode visitOutput(OutputUnitInfo info) {
-    throw Exception("For deferred loading.");
-  }
-
   vm.ProgramInfo build(AllInfo info) {
-    visitAll(info);
+    info.outputUnits.forEach(visitOutput);
+    for (var outputUnitName in outputUnits.keys) {
+      for (var library in info.libraries) {
+        makePackage(library, outputUnitName);
+        makeLibrary(library, outputUnitName);
+      }
+    }
+    for (var outputUnitName in outputUnits.keys) {
+      visitAll(info, outputUnitName);
+      program.root.children[outputUnitName] = outputUnits[outputUnitName]!.root;
+    }
     return program;
   }
-
-  /// Collect libraries into packages and aggregate their sizes.
-  void makePackage(LibraryInfo libraryInfo) {
-    String packageName = libraryGroupName(libraryInfo) ?? libraryInfo.name;
-    vm.ProgramInfoNode? packageInfoNode = packageInfoNodes[packageName];
-    if (packageInfoNode == null) {
-      vm.ProgramInfoNode newPackage = program.makeNode(
-          name: packageName,
-          parent: program.root,
-          type: vm.NodeType.packageNode);
-      newPackage.size = libraryInfo.size;
-      packageInfoNodes[packageName] = newPackage;
-      program.root.children[packageName] = newPackage;
-      outputInfo.add(newPackage);
-      var packageNode = infoNodesByName[newPackage.name];
-      assert(packageNode == null, "encountered package with duplicated name");
-      infoNodesByName[newPackage.name] = newPackage;
-    } else {
-      packageInfoNode.size = (packageInfoNode.size ?? 0) + libraryInfo.size;
-    }
-  }
-
-  void makeLibrary(LibraryInfo libraryInfo) {
-    String packageName = libraryGroupName(libraryInfo) ?? libraryInfo.name;
-    vm.ProgramInfoNode parentNode = infoNodesByName[packageName]!;
-    String libraryName = libraryInfo.name;
-    if (libraryName == '<unnamed>') {
-      libraryName = longName(libraryInfo, useLibraryUri: true, forId: true);
-      unnamedLibraries[libraryInfo] = libraryName;
-    }
-    vm.ProgramInfoNode newLibrary = program.makeNode(
-        name: libraryName, parent: parentNode, type: vm.NodeType.libraryNode);
-    newLibrary.size = libraryInfo.size;
-    parentNode.children[newLibrary.name] = newLibrary;
-    vm.ProgramInfoNode? libraryNode = infoNodesByName[newLibrary.name];
-    assert(libraryNode == null, "encountered library with duplicated name");
-    infoNodesByName[newLibrary.name] = newLibrary;
-    outputInfo.add(newLibrary);
-  }
-
-  void makeFunction(FunctionInfo functionInfo) {
-    Info? parent = functionInfo.parent;
-    if (parent != null) {
-      vm.ProgramInfoNode parentNode;
-      if (parent.name == "<unnamed>" &&
-          parent.kind == kindFromString('library')) {
-        parentNode = infoNodesByName[unnamedLibraries[parent]]!;
-      } else {
-        parentNode = infoNodesByName[parent.name]!;
-      }
-      vm.ProgramInfoNode newFunction = program.makeNode(
-          name: functionInfo.name,
-          parent: parentNode,
-          type: vm.NodeType.functionNode);
-      newFunction.size = functionInfo.size;
-      parentNode.children[newFunction.name] = newFunction;
-      vm.ProgramInfoNode? functionNode = infoNodesByName[newFunction.name];
-      assert(functionNode == null, "encountered function with duplicated name");
-      infoNodesByName[newFunction.name] = newFunction;
-      outputInfo.add(newFunction);
-    }
-  }
-
-  void makeClass(ClassInfo classInfo) {
-    Info? parent = classInfo.parent;
-    if (parent != null) {
-      vm.ProgramInfoNode parentNode;
-      if (parent.name == "<unnamed>" &&
-          parent.kind == kindFromString('library')) {
-        parentNode = infoNodesByName[unnamedLibraries[parent]]!;
-      } else {
-        parentNode = infoNodesByName[parent.name]!;
-      }
-      vm.ProgramInfoNode newClass = program.makeNode(
-          name: classInfo.name,
-          parent: parentNode,
-          type: vm.NodeType.classNode);
-      newClass.size = classInfo.size;
-      parentNode.children[newClass.name] = newClass;
-      vm.ProgramInfoNode? classNode = infoNodesByName[newClass.name];
-      assert(classNode == null, "encountered class with duplicated name");
-      infoNodesByName[newClass.name] = newClass;
-      outputInfo.add(newClass);
-    }
-  }
-
-  /// Fields are currently assigned [vm.NodeType.other].
-  ///
-  /// Note: we might want to create a separate [vm.NodeType.fieldNode] to
-  /// differentiate fields from other miscellaneous nodes for constructing
-  /// the call graph.
-  void makeField(FieldInfo fieldInfo) {
-    Info? parent = fieldInfo.parent;
-    if (parent != null) {
-      vm.ProgramInfoNode parentNode;
-      if (parent.name == "<unnamed>" &&
-          parent.kind == kindFromString('library')) {
-        parentNode = infoNodesByName[unnamedLibraries[parent]]!;
-      } else {
-        parentNode = infoNodesByName[parent.name]!;
-      }
-      vm.ProgramInfoNode newField = program.makeNode(
-          name: fieldInfo.name, parent: parentNode, type: vm.NodeType.other);
-      newField.size = fieldInfo.size;
-      parentNode.children[newField.name] = newField;
-      vm.ProgramInfoNode? fieldNode = infoNodesByName[newField.name];
-      assert(fieldNode == null, "encountered field with duplicated name");
-      infoNodesByName[newField.name] = newField;
-      outputInfo.add(newField);
-    }
-  }
-
-  void makeConstant(ConstantInfo constantInfo) {
-    String constantName = constantInfo.code.first.text!;
-    vm.ProgramInfoNode newConstant = program.makeNode(
-        name: constantName, parent: program.root, type: vm.NodeType.other);
-    newConstant.size = constantInfo.size;
-    program.root.children[newConstant.name] = newConstant;
-    vm.ProgramInfoNode? constantNode = infoNodesByName[newConstant.name];
-    assert(constantNode == null, "encountered constant with duplicated name");
-    infoNodesByName[newConstant.name] = newConstant;
-    outputInfo.add(newConstant);
-  }
-
-  void makeTypedef(TypedefInfo typedefInfo) {
-    vm.ProgramInfoNode newTypedef = program.makeNode(
-        name: typedefInfo.name, parent: program.root, type: vm.NodeType.other);
-    newTypedef.size = typedefInfo.size;
-    infoNodesByName[newTypedef.name] = newTypedef;
-    outputInfo.add(newTypedef);
-  }
-
-  void makeClassType(ClassTypeInfo classTypeInfo) {
-    Info? parent = classTypeInfo.parent;
-    if (parent != null) {
-      vm.ProgramInfoNode parentNode;
-      if (parent.name == "<unnamed>" &&
-          parent.kind == kindFromString('library')) {
-        parentNode = infoNodesByName[unnamedLibraries[parent]]!;
-      } else {
-        parentNode = infoNodesByName[parent.name]!;
-      }
-      vm.ProgramInfoNode newClassType = program.makeNode(
-          name: classTypeInfo.name,
-          parent: parentNode,
-          type: vm.NodeType.other);
-      newClassType.size = classTypeInfo.size;
-      vm.ProgramInfoNode? classTypeNode = infoNodesByName[newClassType.name];
-      assert(
-          classTypeNode == null, "encountered classType with duplicated name");
-      infoNodesByName[newClassType.name] = newClassType;
-      outputInfo.add(newClassType);
-    }
-  }
-
-  void makeClosure(ClosureInfo closureInfo) {
-    Info? parent = closureInfo.parent;
-    if (parent != null) {
-      vm.ProgramInfoNode parentNode;
-      if (parent.name == "<unnamed>" &&
-          parent.kind == kindFromString('library')) {
-        parentNode = infoNodesByName[unnamedLibraries[parent]]!;
-      } else {
-        parentNode = infoNodesByName[parent.name]!;
-      }
-      vm.ProgramInfoNode newClosure = program.makeNode(
-          name: closureInfo.name,
-          parent: parentNode,
-          // ProgramInfo trees consider closures and functions to both be of the functionNode type.
-          type: vm.NodeType.functionNode);
-      newClosure.size = closureInfo.size;
-      parentNode.children[newClosure.name] = newClosure;
-      vm.ProgramInfoNode? closureNode = infoNodesByName[newClosure.name];
-      assert(closureNode == null, "encountered closure with duplicated name");
-      infoNodesByName[newClosure.name] = newClosure;
-      outputInfo.add(newClosure);
-    }
-  }
 }
diff --git a/pkg/dart2js_info/lib/info.dart b/pkg/dart2js_info/lib/info.dart
index 051d75a..7c14f74 100644
--- a/pkg/dart2js_info/lib/info.dart
+++ b/pkg/dart2js_info/lib/info.dart
@@ -203,14 +203,8 @@
       : super(InfoKind.package, name, outputUnit, size, null);
 
   @override
-  T accept<T>(InfoVisitor<T> visitor) {
-    if (visitor is VMProgramInfoVisitor<T>) {
-      return visitor.visitPackage(this);
-    } else {
-      throw ArgumentError(
-          "PackageInfo can only be visited by a VMProgramInfoVisitor");
-    }
-  }
+  T accept<T>(InfoVisitor<T> visitor) =>
+      throw Exception("PackageInfo is not supported by InfoVisitor.");
 }
 
 /// Info associated with a library element.
@@ -620,8 +614,19 @@
 
 /// A visitor that adds implementation for PackageInfo specifically for building
 /// the VM ProgramInfo Tree from a Dart2js info tree.
-abstract class VMProgramInfoVisitor<T> extends InfoVisitor<T> {
-  T visitPackage(PackageInfo info);
+abstract class VMProgramInfoVisitor<T> {
+  T visitAll(AllInfo info, String outputUnit);
+  T visitProgram(ProgramInfo info);
+  T visitPackage(PackageInfo info, String outputUnit);
+  T visitLibrary(LibraryInfo info, String outputUnit);
+  T visitClass(ClassInfo info);
+  T visitClassType(ClassTypeInfo info);
+  T visitField(FieldInfo info);
+  T visitConstant(ConstantInfo info);
+  T visitFunction(FunctionInfo info);
+  T visitTypedef(TypedefInfo info);
+  T visitClosure(ClosureInfo info);
+  T visitOutput(OutputUnitInfo info);
 }
 
 /// A visitor that recursively walks each portion of the program. Because the
@@ -642,6 +647,11 @@
   @override
   void visitProgram(ProgramInfo info) {}
 
+  void visitPackage(PackageInfo info) {
+    throw Exception(
+        "PackageInfo objects are only defined for the VM Devtools format.");
+  }
+
   @override
   void visitLibrary(LibraryInfo info) {
     info.topLevelFunctions.forEach(visitFunction);