[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;
+}