[cfe] Add test for compilation sequence to macro test

This CL adds a test for the minimal sequence of compilation steps
that need to be performed to compile the test while ensuring that
macro declarations are compiled before any application.

Change-Id: Icd6b9140e60d35d2b2b034c16e0807262f971418
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/220768
Reviewed-by: Jens Johansen <jensj@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/front_end/lib/src/fasta/kernel/macro.dart b/pkg/front_end/lib/src/fasta/kernel/macro.dart
index 63dff78..249b4ab 100644
--- a/pkg/front_end/lib/src/fasta/kernel/macro.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/macro.dart
@@ -13,6 +13,7 @@
   Class? macroClass;
   Map<Library, List<Class>> macroDeclarations = {};
   Set<Class> macroClasses = {};
+  List<List<Library>> compilationSequence = [];
 }
 
 class MacroApplicationData {
diff --git a/pkg/front_end/test/macros/data/tests/declare_macro.dart b/pkg/front_end/test/macros/data/tests/declare_macro.dart
index 58edc63..5d396f7 100644
--- a/pkg/front_end/test/macros/data/tests/declare_macro.dart
+++ b/pkg/front_end/test/macros/data/tests/declare_macro.dart
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 
 /*library: 
+ compilationSequence=[
+  package:macro_builder/src/macro.dart,
+  main.dart|package:macro_builder/macro_builder.dart],
  declaredMacros=[MyMacro],
  macrosAreAvailable
 */
diff --git a/pkg/front_end/test/macros/data/tests/declare_vs_apply/apply_lib.dart b/pkg/front_end/test/macros/data/tests/declare_vs_apply/apply_lib.dart
new file mode 100644
index 0000000..f4e6484
--- /dev/null
+++ b/pkg/front_end/test/macros/data/tests/declare_vs_apply/apply_lib.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/*library: 
+ macrosAreApplied,
+ macrosAreAvailable
+*/
+
+import 'macro_lib.dart';
+import 'apply_lib_dep.dart';
+
+@Macro1()
+/*class: Class:
+ appliedMacros=[Macro1],
+ macrosAreApplied
+*/
+class Class extends Super {}
diff --git a/pkg/front_end/test/macros/data/tests/declare_vs_apply/apply_lib_dep.dart b/pkg/front_end/test/macros/data/tests/declare_vs_apply/apply_lib_dep.dart
new file mode 100644
index 0000000..018b909
--- /dev/null
+++ b/pkg/front_end/test/macros/data/tests/declare_vs_apply/apply_lib_dep.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/*library: macrosAreAvailable*/
+
+class Super {}
diff --git a/pkg/front_end/test/macros/data/tests/declare_vs_apply/macro_lib.dart b/pkg/front_end/test/macros/data/tests/declare_vs_apply/macro_lib.dart
new file mode 100644
index 0000000..149d512
--- /dev/null
+++ b/pkg/front_end/test/macros/data/tests/declare_vs_apply/macro_lib.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/*library: 
+ declaredMacros=[Macro1],
+ macrosAreAvailable
+*/
+
+import 'package:macro_builder/macro_builder.dart';
+import 'macro_lib_dep.dart';
+
+class Macro1 extends MacroBase implements Macro {
+  const Macro1();
+}
diff --git a/pkg/front_end/test/macros/data/tests/declare_vs_apply/macro_lib_dep.dart b/pkg/front_end/test/macros/data/tests/declare_vs_apply/macro_lib_dep.dart
new file mode 100644
index 0000000..13c27f8
--- /dev/null
+++ b/pkg/front_end/test/macros/data/tests/declare_vs_apply/macro_lib_dep.dart
@@ -0,0 +1,9 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/*library: macrosAreAvailable*/
+
+class MacroBase {
+  const MacroBase();
+}
diff --git a/pkg/front_end/test/macros/data/tests/declare_vs_apply/main.dart b/pkg/front_end/test/macros/data/tests/declare_vs_apply/main.dart
new file mode 100644
index 0000000..868f08d
--- /dev/null
+++ b/pkg/front_end/test/macros/data/tests/declare_vs_apply/main.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/*library: 
+ compilationSequence=[
+  apply_lib_dep.dart|macro_lib_dep.dart|main_lib_dep.dart|package:macro_builder/src/macro.dart,
+  macro_lib.dart|package:macro_builder/macro_builder.dart,
+  apply_lib.dart|main.dart],
+ macrosAreAvailable
+*/
+
+import 'apply_lib.dart';
+import 'main_lib_dep.dart';
+
+void main() {
+  new Class();
+  method();
+}
diff --git a/pkg/front_end/test/macros/data/tests/declare_vs_apply/main_lib_dep.dart b/pkg/front_end/test/macros/data/tests/declare_vs_apply/main_lib_dep.dart
new file mode 100644
index 0000000..684c9d7
--- /dev/null
+++ b/pkg/front_end/test/macros/data/tests/declare_vs_apply/main_lib_dep.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/*library: macrosAreAvailable*/
+
+void method() {}
diff --git a/pkg/front_end/test/macros/data/tests/direct_import.dart b/pkg/front_end/test/macros/data/tests/direct_import.dart
index b2f6177..f3484cd 100644
--- a/pkg/front_end/test/macros/data/tests/direct_import.dart
+++ b/pkg/front_end/test/macros/data/tests/direct_import.dart
@@ -2,7 +2,12 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-/*library: macrosAreAvailable*/
+/*library: 
+ compilationSequence=[
+  package:macro_builder/src/macro.dart,
+  main.dart],
+ macrosAreAvailable
+*/
 
 // ignore: unused_import
 import 'package:macro_builder/src/macro.dart';
diff --git a/pkg/front_end/test/macros/data/tests/import_macro_builder.dart b/pkg/front_end/test/macros/data/tests/import_macro_builder.dart
index 14e5966c..dca3012 100644
--- a/pkg/front_end/test/macros/data/tests/import_macro_builder.dart
+++ b/pkg/front_end/test/macros/data/tests/import_macro_builder.dart
@@ -2,7 +2,12 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-/*library: macrosAreAvailable*/
+/*library: 
+ compilationSequence=[
+  package:macro_builder/src/macro.dart,
+  main.dart|package:macro_builder/macro_builder.dart],
+ macrosAreAvailable
+*/
 
 // ignore: unused_import
 import 'package:macro_builder/macro_builder.dart';
diff --git a/pkg/front_end/test/macros/data/tests/import_macro_package.dart b/pkg/front_end/test/macros/data/tests/import_macro_package.dart
index 5e69df3..1fabb48 100644
--- a/pkg/front_end/test/macros/data/tests/import_macro_package.dart
+++ b/pkg/front_end/test/macros/data/tests/import_macro_package.dart
@@ -2,7 +2,13 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-/*library: macrosAreAvailable*/
+/*library: 
+ compilationSequence=[
+  package:macro_builder/src/macro.dart,
+  package:macro/macro.dart|package:macro_builder/macro_builder.dart,
+  main.dart],
+ macrosAreAvailable
+*/
 
 // ignore: unused_import
 import 'package:macro/macro.dart';
diff --git a/pkg/front_end/test/macros/data/tests/import_macro_source/main.dart b/pkg/front_end/test/macros/data/tests/import_macro_source/main.dart
index 625315b..5b109f4 100644
--- a/pkg/front_end/test/macros/data/tests/import_macro_source/main.dart
+++ b/pkg/front_end/test/macros/data/tests/import_macro_source/main.dart
@@ -2,7 +2,13 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-/*library: macrosAreAvailable*/
+/*library: 
+ compilationSequence=[
+  package:macro_builder/src/macro.dart,
+  macro_lib.dart|package:macro_builder/macro_builder.dart,
+  main.dart],
+ macrosAreAvailable
+*/
 
 // ignore: unused_import
 import 'macro_lib.dart';
diff --git a/pkg/front_end/test/macros/data/tests/multiple_macros/macro_lib1.dart b/pkg/front_end/test/macros/data/tests/multiple_macros/macro_lib1.dart
new file mode 100644
index 0000000..a05f387
--- /dev/null
+++ b/pkg/front_end/test/macros/data/tests/multiple_macros/macro_lib1.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/*library: 
+ declaredMacros=[Macro1],
+ macrosAreAvailable
+*/
+
+import 'package:macro_builder/macro_builder.dart';
+
+class Macro1 implements Macro {
+  const Macro1();
+}
diff --git a/pkg/front_end/test/macros/data/tests/multiple_macros/macro_lib2a.dart b/pkg/front_end/test/macros/data/tests/multiple_macros/macro_lib2a.dart
new file mode 100644
index 0000000..967801f
--- /dev/null
+++ b/pkg/front_end/test/macros/data/tests/multiple_macros/macro_lib2a.dart
@@ -0,0 +1,14 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/*library: 
+ declaredMacros=[Macro2a],
+ macrosAreAvailable
+*/
+
+import 'package:macro_builder/macro_builder.dart';
+
+class Macro2a implements Macro {
+  const Macro2a();
+}
diff --git a/pkg/front_end/test/macros/data/tests/multiple_macros/macro_lib2b.dart b/pkg/front_end/test/macros/data/tests/multiple_macros/macro_lib2b.dart
new file mode 100644
index 0000000..b9bc7d2
--- /dev/null
+++ b/pkg/front_end/test/macros/data/tests/multiple_macros/macro_lib2b.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/*library: 
+ declaredMacros=[Macro2b],
+ macrosAreApplied,
+ macrosAreAvailable
+*/
+
+import 'package:macro_builder/macro_builder.dart';
+import 'macro_lib2a.dart';
+
+@Macro2a()
+/*class: Macro2b:
+ appliedMacros=[Macro2a],
+ macrosAreApplied
+*/
+class Macro2b implements Macro {
+  const Macro2b();
+}
diff --git a/pkg/front_end/test/macros/data/tests/multiple_macros/main.dart b/pkg/front_end/test/macros/data/tests/multiple_macros/main.dart
new file mode 100644
index 0000000..1d1f1e5
--- /dev/null
+++ b/pkg/front_end/test/macros/data/tests/multiple_macros/main.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2021, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/*library: 
+ compilationSequence=[
+  package:macro_builder/src/macro.dart,
+  macro_lib1.dart|macro_lib2a.dart|package:macro_builder/macro_builder.dart,
+  macro_lib2b.dart,
+  main.dart],
+ macrosAreApplied,
+ macrosAreAvailable
+*/
+
+import 'macro_lib1.dart';
+import 'macro_lib2a.dart';
+import 'macro_lib2b.dart';
+
+@Macro1()
+@Macro2a()
+@Macro2b()
+/*member: main:appliedMacros=[
+  Macro1,
+  Macro2a,
+  Macro2b]*/
+main() {}
diff --git a/pkg/front_end/test/macros/data/tests/no_import.dart b/pkg/front_end/test/macros/data/tests/no_import.dart
index e5c2fad..fbe2463 100644
--- a/pkg/front_end/test/macros/data/tests/no_import.dart
+++ b/pkg/front_end/test/macros/data/tests/no_import.dart
@@ -2,4 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+/*library: compilationSequence=[main.dart]*/
+
 void main() {}
diff --git a/pkg/front_end/test/macros/data/tests/use_macro_package.dart b/pkg/front_end/test/macros/data/tests/use_macro_package.dart
index 09c2b3b..74f18ff 100644
--- a/pkg/front_end/test/macros/data/tests/use_macro_package.dart
+++ b/pkg/front_end/test/macros/data/tests/use_macro_package.dart
@@ -4,6 +4,10 @@
 
 /*library: 
  appliedMacros=[Macro3],
+ compilationSequence=[
+  package:macro_builder/src/macro.dart,
+  package:macro/macro.dart|package:macro_builder/macro_builder.dart,
+  main.dart],
  macrosAreApplied,
  macrosAreAvailable
 */
diff --git a/pkg/front_end/test/macros/data/tests/use_macro_source/main.dart b/pkg/front_end/test/macros/data/tests/use_macro_source/main.dart
index f5a3e60..930eb5c 100644
--- a/pkg/front_end/test/macros/data/tests/use_macro_source/main.dart
+++ b/pkg/front_end/test/macros/data/tests/use_macro_source/main.dart
@@ -3,6 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 
 /*library: 
+ compilationSequence=[
+  package:macro_builder/src/macro.dart,
+  macro_lib.dart|package:macro_builder/macro_builder.dart,
+  main.dart],
  macrosAreApplied,
  macrosAreAvailable
 */
diff --git a/pkg/front_end/test/macros/macro_test.dart b/pkg/front_end/test/macros/macro_test.dart
index bcc7294..8f109a0 100644
--- a/pkg/front_end/test/macros/macro_test.dart
+++ b/pkg/front_end/test/macros/macro_test.dart
@@ -11,6 +11,7 @@
 import 'package:front_end/src/fasta/kernel/macro.dart';
 import 'package:front_end/src/testing/id_testing_helper.dart';
 import 'package:kernel/ast.dart';
+import 'package:kernel/util/graph.dart';
 
 Future<void> main(List<String> args) async {
   enableMacros = true;
@@ -70,10 +71,61 @@
 class Tags {
   static const String macrosAreAvailable = 'macrosAreAvailable';
   static const String macrosAreApplied = 'macrosAreApplied';
+  static const String compilationSequence = 'compilationSequence';
   static const String declaredMacros = 'declaredMacros';
   static const String appliedMacros = 'appliedMacros';
 }
 
+String libraryToString(Library library) {
+  if (library.importUri.scheme == 'package') {
+    return library.importUri.toString();
+  } else if (library.importUri.scheme == 'dart') {
+    return library.importUri.toString();
+  } else {
+    return library.importUri.pathSegments.last;
+  }
+}
+
+String strongComponentToString(Iterable<Library> libraries) {
+  List<String> list = libraries.map(libraryToString).toList();
+  list.sort();
+  return list.join('|');
+}
+
+void computeCompilationSequence(
+    MacroDeclarationData macroDeclarationData, Graph<Library> libraryGraph,
+    {required bool Function(Library) filter}) {
+  List<List<Library>> stronglyConnectedComponents =
+      computeStrongComponents(libraryGraph);
+
+  Graph<List<Library>> strongGraph =
+      new StrongComponentGraph(libraryGraph, stronglyConnectedComponents);
+  List<List<List<Library>>> componentLayers = [];
+  topologicalSort(strongGraph, layers: componentLayers);
+  List<List<Library>> layeredComponents = [];
+  List<Library> currentLayer = [];
+  for (List<List<Library>> layer in componentLayers) {
+    bool declaresMacro = false;
+    for (List<Library> component in layer) {
+      for (Library library in component) {
+        if (filter(library)) continue;
+        if (macroDeclarationData.macroDeclarations.containsKey(library)) {
+          declaresMacro = true;
+        }
+        currentLayer.add(library);
+      }
+    }
+    if (declaresMacro) {
+      layeredComponents.add(currentLayer);
+      currentLayer = [];
+    }
+  }
+  if (currentLayer.isNotEmpty) {
+    layeredComponents.add(currentLayer);
+  }
+  macroDeclarationData.compilationSequence = layeredComponents;
+}
+
 class MacroDataExtractor extends CfeDataExtractor<Features> {
   late final MacroDeclarationData macroDeclarationData;
   late final MacroApplicationData macroApplicationData;
@@ -91,6 +143,13 @@
     return macroApplicationData.libraryData[library];
   }
 
+  List<List<Library>> getCompilationSequence() {
+    computeCompilationSequence(macroDeclarationData,
+        new LibraryGraph(compilerResult.component!.libraries),
+        filter: (Library library) => library.importUri.scheme == 'dart');
+    return macroDeclarationData.compilationSequence;
+  }
+
   MacroApplications? getLibraryMacroApplications(Library library) {
     return getLibraryMacroApplicationData(library)?.libraryApplications;
   }
@@ -129,11 +188,28 @@
   }
 
   @override
+  Features computeClassValue(Id id, Class node) {
+    Features features = new Features();
+    if (getClassMacroApplicationData(node) != null) {
+      features.add(Tags.macrosAreApplied);
+    }
+    registerMacroApplications(features, getClassMacroApplications(node));
+    return features;
+  }
+
+  @override
   Features computeLibraryValue(Id id, Library node) {
     Features features = new Features();
     if (macroDeclarationData.macroClass != null) {
       features.add(Tags.macrosAreAvailable);
     }
+    if (node == compilerResult.component!.mainMethod!.enclosingLibrary) {
+      features.markAsUnsorted(Tags.compilationSequence);
+      for (List<Library> component in getCompilationSequence()) {
+        features.addElement(
+            Tags.compilationSequence, strongComponentToString(component));
+      }
+    }
     List<Class>? macroClasses = macroDeclarationData.macroDeclarations[node];
     if (macroClasses != null) {
       for (Class cls in macroClasses) {
@@ -148,16 +224,6 @@
   }
 
   @override
-  Features computeClassValue(Id id, Class node) {
-    Features features = new Features();
-    if (getClassMacroApplicationData(node) != null) {
-      features.add(Tags.macrosAreApplied);
-    }
-    registerMacroApplications(features, getClassMacroApplications(node));
-    return features;
-  }
-
-  @override
   Features computeMemberValue(Id id, Member node) {
     Features features = new Features();
     registerMacroApplications(features, getMemberMacroApplications(node));
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index fcf3a64..f7058af 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -679,6 +679,7 @@
 launched
 launcher
 layer
+layers
 layout
 lc
 ld
@@ -1215,6 +1216,7 @@
 subexpression
 subexpression's
 subexpressions
+subgraph
 subnode
 subnodes
 subscription
diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt
index 86bfe96..bb52fe8 100644
--- a/pkg/front_end/test/spell_checking_list_tests.txt
+++ b/pkg/front_end/test/spell_checking_list_tests.txt
@@ -598,10 +598,13 @@
 la
 launch
 launching
+layered
 layers
 le
 legs
 lengths
+lib2a
+lib2b
 lightly
 likewise
 lily
@@ -644,6 +647,8 @@
 loopback
 mac
 macro
+macro2a
+macro2b
 macros
 maker
 matters
diff --git a/pkg/kernel/lib/util/graph.dart b/pkg/kernel/lib/util/graph.dart
index 4119f1b..aedbf14 100644
--- a/pkg/kernel/lib/util/graph.dart
+++ b/pkg/kernel/lib/util/graph.dart
@@ -4,12 +4,42 @@
 
 library fasta.graph;
 
+import 'dart:math';
+
+import '../ast.dart';
+
 abstract class Graph<T> {
   Iterable<T> get vertices;
 
   Iterable<T> neighborsOf(T vertex);
 }
 
+/// [Graph] implementation using a collection of [Library] nodes as the graph
+/// vertices and using library dependencies to compute neighbors.
+///
+/// If [coreLibrary] is provided, it will be included in the neighbor of all
+/// vertices. Otherwise, `dart:core` will only be neighboring libraries that
+/// explicitly dependent on it.
+class LibraryGraph implements Graph<Library> {
+  final Iterable<Library> libraries;
+  final Library? coreLibrary;
+
+  LibraryGraph(this.libraries, {this.coreLibrary});
+
+  @override
+  Iterable<Library> get vertices => libraries;
+
+  @override
+  Iterable<Library> neighborsOf(Library library) sync* {
+    if (coreLibrary != null && library != coreLibrary) {
+      yield coreLibrary!;
+    }
+    for (LibraryDependency dependency in library.dependencies) {
+      yield dependency.targetLibrary;
+    }
+  }
+}
+
 /// Computes the strongly connected components of [graph].
 ///
 /// This implementation is based on [Dijkstra's path-based strong component
@@ -77,3 +107,93 @@
 
   return result;
 }
+
+/// A [Graph] using strongly connected components, as computed by
+/// [computeStrongComponents], as vertices. Neighbors are computed using the
+/// neighbors of the provided [subgraph] which was used to compute the strongly
+/// connected components.
+class StrongComponentGraph<T> implements Graph<List<T>> {
+  final Graph<T> subgraph;
+  final List<List<T>> components;
+  final Map<T, List<T>> _elementToComponentMap = {};
+  final Map<List<T>, Set<List<T>>> _neighborsMap = {};
+
+  StrongComponentGraph(this.subgraph, this.components) {
+    for (List<T> component in components) {
+      for (T element in component) {
+        _elementToComponentMap[element] = component;
+      }
+    }
+  }
+
+  Set<List<T>> _computeNeighborsOf(List<T> component) {
+    Set<List<T>> neighbors = {};
+    for (T element in component) {
+      for (T neighborElement in subgraph.neighborsOf(element)) {
+        List<T> neighborComponent = _elementToComponentMap[neighborElement]!;
+        if (component != neighborComponent) {
+          neighbors.add(neighborComponent);
+        }
+      }
+    }
+    return neighbors;
+  }
+
+  @override
+  Iterable<List<T>> neighborsOf(List<T> vertex) {
+    return _neighborsMap[vertex] ??= _computeNeighborsOf(vertex);
+  }
+
+  @override
+  Iterable<List<T>> get vertices => components;
+}
+
+/// Returns the non-cyclic vertices of [graph] sorted in topological order.
+///
+/// If [indexMap] is provided, it is filled with "index" of each vertex.
+/// If [layers] is provided, it is filled with a list of the vertices for each
+/// "index".
+///
+/// Here, the "index" of a vertex is the length of the longest path through
+/// neighbors. For vertices with no neighbors, the index is 0. For any other
+/// vertex, it is 1 plus max of the index of its neighbors.
+List<T> topologicalSort<T>(Graph<T> graph,
+    {Map<T, int>? indexMap, List<List<T>>? layers}) {
+  List<T> workList = graph.vertices.toList();
+  indexMap ??= {};
+  List<T> topologicallySortedVertices = [];
+  List<T> previousWorkList;
+  do {
+    previousWorkList = workList;
+    workList = [];
+    for (int i = 0; i < previousWorkList.length; i++) {
+      T vertex = previousWorkList[i];
+      int index = 0;
+      bool allSupertypesProcessed = true;
+      for (T neighbor in graph.neighborsOf(vertex)) {
+        int? neighborIndex = indexMap[neighbor];
+        if (neighborIndex == null) {
+          allSupertypesProcessed = false;
+          break;
+        } else {
+          index = max(index, neighborIndex + 1);
+        }
+      }
+      if (allSupertypesProcessed) {
+        indexMap[vertex] = index;
+        topologicallySortedVertices.add(vertex);
+        if (layers != null) {
+          if (index >= layers.length) {
+            assert(index == layers.length);
+            layers.add([vertex]);
+          } else {
+            layers[index].add(vertex);
+          }
+        }
+      } else {
+        workList.add(vertex);
+      }
+    }
+  } while (previousWorkList.length != workList.length);
+  return topologicallySortedVertices;
+}