[analyzer] Faster export scope calculation in bad cases

With the `big_chain_benchmark` for ImportExportCycle and
ImportCycleExportChain it scales a lot better. The other ones
(ImportCycle, ImportChain, ImportExportChain) doesn't change.


For ImportExportCycle, in AOT mode, before we got:

```
+------+-----------+------------+------------+
| Size |  Initial  | Completion | Fully done |
+------+-----------+------------+------------+
|   16 |   0.46673 |   0.169486 |   0.406448 |
|   32 |  0.875871 |   0.242876 |    0.85543 |
|   64 |  1.583077 |   0.465915 |   1.506953 |
|  128 |  3.198071 |   0.903894 |    3.09165 |
|  256 |  6.786677 |   2.149489 |   6.779569 |
|  512 | 17.346131 |    8.92149 |  17.971033 |
| 1024 | 63.358453 |  46.152089 |  65.401559 |
+------+-----------+------------+------------+
```


now instead we get:

```
+------+-----------+------------+------------+
| Size |  Initial  | Completion | Fully done |
+------+-----------+------------+------------+
|   16 |  0.474942 |   0.166857 |   0.411457 |
|   32 |  0.834925 |   0.243397 |    0.76843 |
|   64 |  1.608813 |   0.439885 |    1.53438 |
|  128 |  3.198453 |   0.805756 |   3.302559 |
|  256 |  6.347211 |   1.712426 |   6.165624 |
|  512 | 12.874747 |   3.551489 |    13.1461 |
| 1024 |  27.14403 |   7.844261 |   27.32785 |
+------+-----------+------------+------------+
```

An almost 6x improvement for completion and 2x+ improvement for both
initial analysis and analysing after the change.


For ImportCycleExportChain, in AOT mode, before we got:

```
+------+-----------+------------+------------+
| Size |  Initial  | Completion | Fully done |
+------+-----------+------------+------------+
|   16 |  0.454349 |   0.152505 |   0.400889 |
|   32 |  0.892146 |   0.302407 |    0.81263 |
|   64 |   1.67604 |   0.439954 |   1.517379 |
|  128 |  3.335087 |   0.907002 |   3.262481 |
|  256 |  6.378526 |   1.943725 |   6.292922 |
|  512 | 15.477861 |   6.461358 |  15.560008 |
| 1024 | 50.671197 |   33.14349 |  51.689455 |
+------+-----------+------------+------------+
```


now instead we get:

```
+------+-----------+------------+------------+
| Size |  Initial  | Completion | Fully done |
+------+-----------+------------+------------+
|   16 |  0.548357 |   0.166257 |   0.415632 |
|   32 |  0.906483 |   0.296618 |   0.910544 |
|   64 |  1.623723 |   0.464061 |   1.527888 |
|  128 |  3.067769 |   0.801507 |   2.927981 |
|  256 |  6.224551 |   1.596711 |   6.098531 |
|  512 | 12.941757 |   3.277487 |  12.345753 |
| 1024 | 25.870713 |   7.020787 |  25.760605 |
+------+-----------+------------+------------+
```

A ~4.7x improvement on completion and ~2x improvement for both initial
analysis and analysing after the change.

Change-Id: I052879c1695ad5f0aa63c2d96013a1e3eae96428
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/413421
Reviewed-by: Lasse Nielsen <lrn@google.com>
Commit-Queue: Jens Johansen <jensj@google.com>
diff --git a/pkg/analyzer/lib/src/summary2/export.dart b/pkg/analyzer/lib/src/summary2/export.dart
index 0a20b67..542214a 100644
--- a/pkg/analyzer/lib/src/summary2/export.dart
+++ b/pkg/analyzer/lib/src/summary2/export.dart
@@ -61,6 +61,7 @@
   });
 
   void addLocation(ExportLocation location) {
+    // This list is very small, contains on it is probably ok.
     if (!locations.contains(location)) {
       locations.add(location);
     }
diff --git a/pkg/analyzer/lib/src/summary2/link.dart b/pkg/analyzer/lib/src/summary2/link.dart
index a908e30..bd801f6 100644
--- a/pkg/analyzer/lib/src/summary2/link.dart
+++ b/pkg/analyzer/lib/src/summary2/link.dart
@@ -15,6 +15,7 @@
 import 'package:analyzer/src/fine/library_manifest.dart';
 import 'package:analyzer/src/summary2/bundle_writer.dart';
 import 'package:analyzer/src/summary2/detach_nodes.dart';
+import 'package:analyzer/src/summary2/export.dart';
 import 'package:analyzer/src/summary2/library_builder.dart';
 import 'package:analyzer/src/summary2/linked_element_factory.dart';
 import 'package:analyzer/src/summary2/reference.dart';
@@ -179,19 +180,47 @@
       }
     }
 
-    while (true) {
-      var hasChanges = false;
+    // We keep a queue of exports to propagate.
+    // First we loop over every reference that both exports and is exported,
+    // but then we only process the exports that should be propagated.
+    var additionalExportData = <_AdditionalExport>[];
+
+    void addExport(Export export, String name, ExportedReference reference) {
+      if (export.addToExportScope(name, reference)) {
+        // We've added [name] to [export.exporter]s export scope.
+        // We need to propagate that to anyone that exports that library.
+        additionalExportData
+            .add(_AdditionalExport(export.exporter, name, reference));
+      }
+    }
+
+    for (var exported in both) {
+      for (var export in exported.exports) {
+        exported.exportScope.forEach((name, reference) {
+          addExport(export, name, reference);
+        });
+      }
+    }
+
+    while (additionalExportData.isNotEmpty) {
+      var data = additionalExportData.removeLast();
+      for (var export in data.exported.exports) {
+        addExport(export, data.name, data.reference);
+      }
+    }
+
+    assert(() {
       for (var exported in both) {
         for (var export in exported.exports) {
           exported.exportScope.forEach((name, reference) {
             if (export.addToExportScope(name, reference)) {
-              hasChanges = true;
+              throw "Error in export calculation: Assert failed.";
             }
           });
         }
       }
-      if (!hasChanges) break;
-    }
+      return true;
+    }(), true);
 
     for (var library in builders.values) {
       library.storeExportScope();
@@ -362,3 +391,11 @@
     required this.resolutionBytes,
   });
 }
+
+class _AdditionalExport {
+  final LibraryBuilder exported;
+  final String name;
+  final ExportedReference reference;
+
+  _AdditionalExport(this.exported, this.name, this.reference);
+}
diff --git a/tests/language/export/bigger_cyclic_helper_1.dart b/tests/language/export/bigger_cyclic_helper_1.dart
new file mode 100644
index 0000000..b9a8a99
--- /dev/null
+++ b/tests/language/export/bigger_cyclic_helper_1.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2025, 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.
+
+import "bigger_cyclic_helper_2.dart";
+export "bigger_cyclic_helper_2.dart";
+
+void get2(int i) {
+  print("2: $i");
+  if (i > 0) {
+    get1(i - 1);
+    get2(i - 1);
+    get3(i - 1);
+    get4(i - 1);
+    get5(i - 1);
+    get6(i - 1);
+  }
+}
diff --git a/tests/language/export/bigger_cyclic_helper_2.dart b/tests/language/export/bigger_cyclic_helper_2.dart
new file mode 100644
index 0000000..3857708
--- /dev/null
+++ b/tests/language/export/bigger_cyclic_helper_2.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2025, 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.
+
+import "bigger_cyclic_helper_3.dart";
+export "bigger_cyclic_helper_3.dart";
+
+void get3(int i) {
+  print("3: $i");
+  if (i > 0) {
+    get1(i - 1);
+    get2(i - 1);
+    get3(i - 1);
+    get4(i - 1);
+    get5(i - 1);
+    get6(i - 1);
+  }
+}
diff --git a/tests/language/export/bigger_cyclic_helper_3.dart b/tests/language/export/bigger_cyclic_helper_3.dart
new file mode 100644
index 0000000..109347f
--- /dev/null
+++ b/tests/language/export/bigger_cyclic_helper_3.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2025, 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.
+
+import "bigger_cyclic_helper_4.dart";
+export "bigger_cyclic_helper_4.dart";
+
+void get4(int i) {
+  print("4: $i");
+  if (i > 0) {
+    get1(i - 1);
+    get2(i - 1);
+    get3(i - 1);
+    get4(i - 1);
+    get5(i - 1);
+    get6(i - 1);
+  }
+}
diff --git a/tests/language/export/bigger_cyclic_helper_4.dart b/tests/language/export/bigger_cyclic_helper_4.dart
new file mode 100644
index 0000000..00bea4e
--- /dev/null
+++ b/tests/language/export/bigger_cyclic_helper_4.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2025, 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.
+
+import "bigger_cyclic_helper_5.dart";
+export "bigger_cyclic_helper_5.dart";
+
+void get5(int i) {
+  print("5: $i");
+  if (i > 0) {
+    get1(i - 1);
+    get2(i - 1);
+    get3(i - 1);
+    get4(i - 1);
+    get5(i - 1);
+    get6(i - 1);
+  }
+}
diff --git a/tests/language/export/bigger_cyclic_helper_5.dart b/tests/language/export/bigger_cyclic_helper_5.dart
new file mode 100644
index 0000000..4b1086a
--- /dev/null
+++ b/tests/language/export/bigger_cyclic_helper_5.dart
@@ -0,0 +1,18 @@
+// Copyright (c) 2025, 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.
+
+import "bigger_cyclic_test.dart";
+export "bigger_cyclic_test.dart";
+
+void get6(int i) {
+  print("6: $i");
+  if (i > 0) {
+    get1(i - 1);
+    get2(i - 1);
+    get3(i - 1);
+    get4(i - 1);
+    get5(i - 1);
+    get6(i - 1);
+  }
+}
diff --git a/tests/language/export/bigger_cyclic_test.dart b/tests/language/export/bigger_cyclic_test.dart
new file mode 100644
index 0000000..b321be0
--- /dev/null
+++ b/tests/language/export/bigger_cyclic_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2025, 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.
+
+import "bigger_cyclic_helper_1.dart";
+export "bigger_cyclic_helper_1.dart";
+
+void main() {
+  get1(1);
+}
+
+void get1(int i) {
+  print("1: $i");
+  if (i > 0) {
+    get1(i - 1);
+    get2(i - 1);
+    get3(i - 1);
+    get4(i - 1);
+    get5(i - 1);
+    get6(i - 1);
+  }
+}