Track async microtasks scheduled by macros, and ensure they have all completed before a macro returns.

Does not track certain events such as async I/O, but will refuse to execute the scheduled callbacks when those do complete.

Bug: https://github.com/dart-lang/sdk/issues/55426
Change-Id: Ie81100e9e4dbe49d050bad875cc9b6a65969863d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/370120
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Morgan :) <davidmorgan@google.com>
Auto-Submit: Jake Macdonald <jakemac@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/_macros/CHANGELOG.md b/pkg/_macros/CHANGELOG.md
index 5fa4a7d..ac85572 100644
--- a/pkg/_macros/CHANGELOG.md
+++ b/pkg/_macros/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.3.1
+
+- Make it an error for macros to complete with pending async work scheduled.
+
 ## 0.3.0
 
 - Remove type parameter on internal `StaticType` implementation.
diff --git a/pkg/_macros/lib/src/executor/client.dart b/pkg/_macros/lib/src/executor/client.dart
index 5be2a4c..8dfc7814 100644
--- a/pkg/_macros/lib/src/executor/client.dart
+++ b/pkg/_macros/lib/src/executor/client.dart
@@ -215,8 +215,8 @@
           remoteInstance: request.introspector,
           serializationZoneId: request.serializationZoneId);
 
-      MacroExecutionResult result =
-          await executeTypesMacro(instance, request.target, introspector);
+      MacroExecutionResult result = await runPhase(
+          () => executeTypesMacro(instance, request.target, introspector));
       return SerializableResponse(
           responseType: MessageType.macroExecutionResult,
           response: result,
@@ -244,8 +244,8 @@
               remoteInstance: request.introspector,
               serializationZoneId: request.serializationZoneId);
 
-      MacroExecutionResult result = await executeDeclarationsMacro(
-          instance, request.target, introspector);
+      MacroExecutionResult result = await runPhase(() =>
+          executeDeclarationsMacro(instance, request.target, introspector));
       return SerializableResponse(
           responseType: MessageType.macroExecutionResult,
           response: result,
@@ -272,8 +272,8 @@
               remoteInstance: request.introspector,
               serializationZoneId: request.serializationZoneId);
 
-      MacroExecutionResult result =
-          await executeDefinitionMacro(instance, request.target, introspector);
+      MacroExecutionResult result = await runPhase(
+          () => executeDefinitionMacro(instance, request.target, introspector));
       return SerializableResponse(
           responseType: MessageType.macroExecutionResult,
           response: result,
@@ -339,3 +339,85 @@
             'ProcessExecutor');
       }
     };
+
+/// Runs [phase] in a [Zone] which tracks scheduled tasks, completing with a
+/// [StateError] if [phase] returns a value while additional tasks or timers
+/// are still scheduled.
+Future<MacroExecutionResult> runPhase(
+    Future<MacroExecutionResult> Function() phase) {
+  final completer = Completer<MacroExecutionResult>();
+
+  var pendingMicrotasks = 0;
+  var activeTimers = 0;
+  Zone.current
+      .fork(
+          specification: ZoneSpecification(
+        handleUncaughtError: (self, parent, zone, error, stackTrace) {
+          if (completer.isCompleted) return;
+          completer.completeError(error, stackTrace);
+        },
+        createTimer: (self, parent, zone, duration, f) {
+          activeTimers++;
+          return _WrappedTimer(
+              parent.createTimer(zone, duration, () {
+                activeTimers--;
+                f();
+              }),
+              onCancel: () => activeTimers--);
+        },
+        createPeriodicTimer: (self, parent, zone, duration, f) {
+          activeTimers++;
+          return _WrappedTimer(parent.createPeriodicTimer(zone, duration, f),
+              onCancel: () => activeTimers--);
+        },
+        scheduleMicrotask: (self, parent, zone, f) {
+          pendingMicrotasks++;
+          parent.scheduleMicrotask(zone, () {
+            pendingMicrotasks--;
+            assert(pendingMicrotasks >= 0);
+            // This should only happen if we have previously competed with an
+            // error. Just skip this scheduled task in that case.
+            if (completer.isCompleted) return;
+            f();
+          });
+        },
+      ))
+      .runGuarded(() => phase().then((value) {
+            if (completer.isCompleted) return;
+            if (pendingMicrotasks != 0) {
+              throw StateError(
+                  'Macro completed but has $pendingMicrotasks async tasks still '
+                  'pending. Macros must complete all async work prior to '
+                  'returning.');
+            }
+            if (activeTimers != 0) {
+              throw StateError(
+                  'Macro completed but has $activeTimers active timers. '
+                  'Macros must cancel all timers prior to returning.');
+            }
+            completer.complete(value);
+          }));
+  return completer.future;
+}
+
+/// Wraps a [Timer] to track when it is cancelled and calls [onCancel], if the
+/// timer is still active.
+class _WrappedTimer implements Timer {
+  final Timer timer;
+
+  final void Function() onCancel;
+
+  _WrappedTimer(this.timer, {required this.onCancel});
+
+  @override
+  void cancel() {
+    if (isActive) onCancel();
+    timer.cancel();
+  }
+
+  @override
+  bool get isActive => timer.isActive;
+
+  @override
+  int get tick => timer.tick;
+}
diff --git a/pkg/_macros/pubspec.yaml b/pkg/_macros/pubspec.yaml
index d2ee9ab..a63458b 100644
--- a/pkg/_macros/pubspec.yaml
+++ b/pkg/_macros/pubspec.yaml
@@ -1,5 +1,5 @@
 name: _macros
-version: 0.3.0
+version: 0.3.1
 description: >-
   This is a private SDK vendored package, which is re-exported by the public
   `macros` package, which is a pub package. Every change to this package is
diff --git a/pkg/_macros/test/executor/executor_test.dart b/pkg/_macros/test/executor/executor_test.dart
index 929eed2..89f32ce 100644
--- a/pkg/_macros/test/executor/executor_test.dart
+++ b/pkg/_macros/test/executor/executor_test.dart
@@ -21,8 +21,14 @@
 void main() {
   late MacroExecutor executor;
   late File kernelOutputFile;
+  final danglingTaskMacroName = 'DanglingTaskMacro';
+  final danglingTimerMacroName = 'DanglingTimerMacro';
+  final danglingPeriodicTimerMacroName = 'DanglingPeriodicTimerMacro';
   final diagnosticMacroName = 'DiagnosticMacro';
   final simpleMacroName = 'SimpleMacro';
+  late MacroInstanceIdentifier danglingTaskMacroId;
+  late MacroInstanceIdentifier danglingTimerMacroId;
+  late MacroInstanceIdentifier danglingPeriodicTimerMacroId;
   late MacroInstanceIdentifier diagnosticMacroInstanceId;
   late MacroInstanceIdentifier simpleMacroInstanceId;
   late Uri macroUri;
@@ -46,6 +52,9 @@
             var bootstrapContent = bootstrapMacroIsolate({
               macroUri.toString(): {
                 simpleMacroName: ['', 'named'],
+                danglingTaskMacroName: [''],
+                danglingTimerMacroName: [''],
+                danglingPeriodicTimerMacroName: [''],
                 diagnosticMacroName: [''],
               }
             }, mode);
@@ -129,6 +138,21 @@
             expect(simpleMacroInstanceId, isNotNull,
                 reason: 'Can create an instance with named arguments.');
 
+            danglingTaskMacroId = await executor.instantiateMacro(
+                macroUri, danglingTaskMacroName, '', Arguments([], {}));
+            expect(danglingTaskMacroId, isNotNull);
+
+            danglingTimerMacroId = await executor.instantiateMacro(
+                macroUri, danglingTimerMacroName, '', Arguments([], {}));
+            expect(danglingTimerMacroId, isNotNull);
+
+            danglingPeriodicTimerMacroId = await executor.instantiateMacro(
+                macroUri,
+                danglingPeriodicTimerMacroName,
+                '',
+                Arguments([], {}));
+            expect(danglingPeriodicTimerMacroId, isNotNull);
+
             diagnosticMacroInstanceId = await executor.instantiateMacro(
                 macroUri, diagnosticMacroName, '', Arguments([], {}));
             expect(diagnosticMacroInstanceId, isNotNull);
@@ -401,6 +425,36 @@
 '''),
                 );
               });
+
+              test('Cannot leave dangling tasks', () async {
+                expect(
+                    () => executor.executeTypesPhase(danglingTaskMacroId,
+                        Fixtures.library, TestTypePhaseIntrospector()),
+                    throwsA(
+                      isA<UnexpectedMacroException>().having((e) => e.message,
+                          'message', contains('1 async tasks still pending')),
+                    ));
+              });
+
+              test('Cannot leave dangling timers', () async {
+                expect(
+                    () => executor.executeTypesPhase(danglingTimerMacroId,
+                        Fixtures.library, TestTypePhaseIntrospector()),
+                    throwsA(
+                      isA<UnexpectedMacroException>().having((e) => e.message,
+                          'message', contains('1 active timers')),
+                    ));
+
+                expect(
+                    () => executor.executeTypesPhase(
+                        danglingPeriodicTimerMacroId,
+                        Fixtures.library,
+                        TestTypePhaseIntrospector()),
+                    throwsA(
+                      isA<UnexpectedMacroException>().having((e) => e.message,
+                          'message', contains('1 active timers')),
+                    ));
+              });
             });
 
             group('in the declaration phase', () {
@@ -666,6 +720,40 @@
                   equalsIgnoringWhitespace('final LibraryInfo library;'),
                 );
               });
+
+              test('Cannot leave dangling tasks', () async {
+                expect(
+                    () => executor.executeDeclarationsPhase(
+                        danglingTaskMacroId,
+                        Fixtures.library,
+                        Fixtures.testDeclarationPhaseIntrospector),
+                    throwsA(
+                      isA<UnexpectedMacroException>().having((e) => e.message,
+                          'message', contains('1 async tasks still pending')),
+                    ));
+              });
+
+              test('Cannot leave dangling timers', () async {
+                expect(
+                    () => executor.executeDeclarationsPhase(
+                        danglingTimerMacroId,
+                        Fixtures.library,
+                        Fixtures.testDeclarationPhaseIntrospector),
+                    throwsA(
+                      isA<UnexpectedMacroException>().having((e) => e.message,
+                          'message', contains('1 active timers')),
+                    ));
+
+                expect(
+                    () => executor.executeDeclarationsPhase(
+                        danglingPeriodicTimerMacroId,
+                        Fixtures.library,
+                        Fixtures.testDeclarationPhaseIntrospector),
+                    throwsA(
+                      isA<UnexpectedMacroException>().having((e) => e.message,
+                          'message', contains('1 active timers')),
+                    ));
+              });
             });
 
             group('in the definition phase', () {
@@ -961,6 +1049,40 @@
 augment final LibraryInfo library = LibraryInfo(Uri.parse('package:foo/bar.dart'), '3.0', [MyClass, MyEnum, MyMixin, ]);
 '''));
               });
+
+              test('Cannot leave dangling tasks', () async {
+                expect(
+                    () => executor.executeDefinitionsPhase(
+                        danglingTaskMacroId,
+                        Fixtures.library,
+                        Fixtures.testDefinitionPhaseIntrospector),
+                    throwsA(
+                      isA<UnexpectedMacroException>().having((e) => e.message,
+                          'message', contains('1 async tasks still pending')),
+                    ));
+              });
+
+              test('Cannot leave dangling timers', () async {
+                expect(
+                    () => executor.executeDefinitionsPhase(
+                        danglingTimerMacroId,
+                        Fixtures.library,
+                        Fixtures.testDefinitionPhaseIntrospector),
+                    throwsA(
+                      isA<UnexpectedMacroException>().having((e) => e.message,
+                          'message', contains('1 active timers')),
+                    ));
+
+                expect(
+                    () => executor.executeDefinitionsPhase(
+                        danglingPeriodicTimerMacroId,
+                        Fixtures.library,
+                        Fixtures.testDefinitionPhaseIntrospector),
+                    throwsA(
+                      isA<UnexpectedMacroException>().having((e) => e.message,
+                          'message', contains('1 active timers')),
+                    ));
+              });
             });
 
             test('and report diagnostics', () async {
diff --git a/pkg/_macros/test/executor/simple_macro.dart b/pkg/_macros/test/executor/simple_macro.dart
index 4f110fe..c595707 100644
--- a/pkg/_macros/test/executor/simple_macro.dart
+++ b/pkg/_macros/test/executor/simple_macro.dart
@@ -802,6 +802,75 @@
   ]);
 }
 
+class DanglingTaskMacro
+    implements
+        LibraryTypesMacro,
+        LibraryDeclarationsMacro,
+        LibraryDefinitionMacro {
+  @override
+  void buildTypesForLibrary(Library library, TypeBuilder builder) {
+    Future.value('hello').then((_) => Future.value('world'));
+  }
+
+  @override
+  void buildDeclarationsForLibrary(
+      Library library, DeclarationBuilder builder) {
+    Future.value('hello').then((_) => Future.value('world'));
+  }
+
+  @override
+  FutureOr<void> buildDefinitionForLibrary(
+      Library library, LibraryDefinitionBuilder builder) {
+    Future.value('hello').then((_) => Future.value('world'));
+  }
+}
+
+class DanglingTimerMacro
+    implements
+        LibraryTypesMacro,
+        LibraryDeclarationsMacro,
+        LibraryDefinitionMacro {
+  @override
+  void buildTypesForLibrary(Library library, TypeBuilder builder) {
+    Timer.run(() {});
+  }
+
+  @override
+  void buildDeclarationsForLibrary(
+      Library library, DeclarationBuilder builder) {
+    Timer.run(() {});
+  }
+
+  @override
+  FutureOr<void> buildDefinitionForLibrary(
+      Library library, LibraryDefinitionBuilder builder) {
+    Timer.run(() {});
+  }
+}
+
+class DanglingPeriodicTimerMacro
+    implements
+        LibraryTypesMacro,
+        LibraryDeclarationsMacro,
+        LibraryDefinitionMacro {
+  @override
+  void buildTypesForLibrary(Library library, TypeBuilder builder) {
+    Timer.periodic(Duration(seconds: 1), (_) {});
+  }
+
+  @override
+  void buildDeclarationsForLibrary(
+      Library library, DeclarationBuilder builder) {
+    Timer.periodic(Duration(seconds: 1), (_) {});
+  }
+
+  @override
+  FutureOr<void> buildDefinitionForLibrary(
+      Library library, LibraryDefinitionBuilder builder) {
+    Timer.periodic(Duration(seconds: 1), (_) {});
+  }
+}
+
 extension on String {
   String capitalize() => '${this[0].toUpperCase()}${substring(1)}';
 }
diff --git a/pkg/front_end/test/macros/application/data/tests/crash.dart.expect b/pkg/front_end/test/macros/application/data/tests/crash.dart.expect
index 5373a5b..a18d56f 100644
--- a/pkg/front_end/test/macros/application/data/tests/crash.dart.expect
+++ b/pkg/front_end/test/macros/application/data/tests/crash.dart.expect
@@ -8,22 +8,28 @@
 // org-dartlang-test:///a/b/c/main.dart:7:2: Context: Error in buildTypesForClass
 // #0      CrashTypesMacro.buildTypesForClass (package:macro/crash.dart:13:5)
 // #1      executeTypesMacro (package:_macros/src/executor/execute_macro.dart:39:21)
-// #2      MacroExpansionClient._executeTypesPhase (package:_macros/src/executor/client.dart:219:17)
-// #3      MacroExpansionClient._handleMessage.<anonymous closure> (package:_macros/src/executor/client.dart:145:18)
+// #2      MacroExpansionClient._executeTypesPhase.<anonymous closure> (package:_macros/src/executor/client.dart:219:17)
+// #3      runPhase.<anonymous closure> (package:_macros/src/executor/client.dart:385:30)
 // #4      _rootRun (dart:async/zone.dart:1399:13)
 // #5      _CustomZone.run (dart:async/zone.dart:1301:19)
-// #6      withRemoteInstanceZone (package:_macros/src/executor/remote_instance.dart:172:15)
-// #7      MacroExpansionClient._handleMessage (package:_macros/src/executor/client.dart:118:11)
-// #8      new MacroExpansionClient._.<anonymous closure> (package:_macros/src/executor/client.dart:37:39)
-// #9      _rootRunUnary (dart:async/zone.dart:1415:13)
-// #10     _CustomZone.runUnary (dart:async/zone.dart:1308:19)
-// #11     _CustomZone.runUnaryGuarded (dart:async/zone.dart:1217:7)
-// #12     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:365:11)
-// #13     _BufferingStreamSubscription._add (dart:async/stream_impl.dart:297:7)
-// #14     _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:784:19)
-// #15     _StreamController._add (dart:async/stream_controller.dart:658:7)
-// #16     _StreamController.add (dart:async/stream_controller.dart:606:5)
-// #17     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
+// #6      _CustomZone.runGuarded (dart:async/zone.dart:1209:7)
+// #7      runPhase (package:_macros/src/executor/client.dart:385:8)
+// #8      MacroExpansionClient._executeTypesPhase (package:_macros/src/executor/client.dart:218:43)
+// #9      MacroExpansionClient._handleMessage.<anonymous closure> (package:_macros/src/executor/client.dart:145:18)
+// #10     _rootRun (dart:async/zone.dart:1399:13)
+// #11     _CustomZone.run (dart:async/zone.dart:1301:19)
+// #12     withRemoteInstanceZone (package:_macros/src/executor/remote_instance.dart:172:15)
+// #13     MacroExpansionClient._handleMessage (package:_macros/src/executor/client.dart:118:11)
+// #14     new MacroExpansionClient._.<anonymous closure> (package:_macros/src/executor/client.dart:37:39)
+// #15     _rootRunUnary (dart:async/zone.dart:1415:13)
+// #16     _CustomZone.runUnary (dart:async/zone.dart:1308:19)
+// #17     _CustomZone.runUnaryGuarded (dart:async/zone.dart:1217:7)
+// #18     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:365:11)
+// #19     _BufferingStreamSubscription._add (dart:async/stream_impl.dart:297:7)
+// #20     _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:784:19)
+// #21     _StreamController._add (dart:async/stream_controller.dart:658:7)
+// #22     _StreamController.add (dart:async/stream_controller.dart:606:5)
+// #23     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
 //
 // @CrashTypesMacro()
 //  ^
@@ -34,22 +40,28 @@
 // org-dartlang-test:///a/b/c/main.dart:8:2: Context: Error in buildDeclarationsForClass
 // #0      CrashDeclarationsMacro.buildDeclarationsForClass (package:macro/crash.dart:22:5)
 // #1      executeDeclarationsMacro (package:_macros/src/executor/execute_macro.dart:107:21)
-// #2      MacroExpansionClient._executeDeclarationsPhase (package:_macros/src/executor/client.dart:247:43)
-// #3      MacroExpansionClient._handleMessage.<anonymous closure> (package:_macros/src/executor/client.dart:135:18)
+// #2      MacroExpansionClient._executeDeclarationsPhase.<anonymous closure> (package:_macros/src/executor/client.dart:248:11)
+// #3      runPhase.<anonymous closure> (package:_macros/src/executor/client.dart:385:30)
 // #4      _rootRun (dart:async/zone.dart:1399:13)
 // #5      _CustomZone.run (dart:async/zone.dart:1301:19)
-// #6      withRemoteInstanceZone (package:_macros/src/executor/remote_instance.dart:172:15)
-// #7      MacroExpansionClient._handleMessage (package:_macros/src/executor/client.dart:118:11)
-// #8      new MacroExpansionClient._.<anonymous closure> (package:_macros/src/executor/client.dart:37:39)
-// #9      _rootRunUnary (dart:async/zone.dart:1415:13)
-// #10     _CustomZone.runUnary (dart:async/zone.dart:1308:19)
-// #11     _CustomZone.runUnaryGuarded (dart:async/zone.dart:1217:7)
-// #12     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:365:11)
-// #13     _BufferingStreamSubscription._add (dart:async/stream_impl.dart:297:7)
-// #14     _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:784:19)
-// #15     _StreamController._add (dart:async/stream_controller.dart:658:7)
-// #16     _StreamController.add (dart:async/stream_controller.dart:606:5)
-// #17     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
+// #6      _CustomZone.runGuarded (dart:async/zone.dart:1209:7)
+// #7      runPhase (package:_macros/src/executor/client.dart:385:8)
+// #8      MacroExpansionClient._executeDeclarationsPhase (package:_macros/src/executor/client.dart:247:43)
+// #9      MacroExpansionClient._handleMessage.<anonymous closure> (package:_macros/src/executor/client.dart:135:18)
+// #10     _rootRun (dart:async/zone.dart:1399:13)
+// #11     _CustomZone.run (dart:async/zone.dart:1301:19)
+// #12     withRemoteInstanceZone (package:_macros/src/executor/remote_instance.dart:172:15)
+// #13     MacroExpansionClient._handleMessage (package:_macros/src/executor/client.dart:118:11)
+// #14     new MacroExpansionClient._.<anonymous closure> (package:_macros/src/executor/client.dart:37:39)
+// #15     _rootRunUnary (dart:async/zone.dart:1415:13)
+// #16     _CustomZone.runUnary (dart:async/zone.dart:1308:19)
+// #17     _CustomZone.runUnaryGuarded (dart:async/zone.dart:1217:7)
+// #18     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:365:11)
+// #19     _BufferingStreamSubscription._add (dart:async/stream_impl.dart:297:7)
+// #20     _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:784:19)
+// #21     _StreamController._add (dart:async/stream_controller.dart:658:7)
+// #22     _StreamController.add (dart:async/stream_controller.dart:606:5)
+// #23     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
 //
 // @CrashDeclarationsMacro()
 //  ^
@@ -60,22 +72,28 @@
 // org-dartlang-test:///a/b/c/main.dart:9:2: Context: Error in buildDefinitionForClass
 // #0      CrashDefinitionMacro.buildDefinitionForClass (package:macro/crash.dart:31:5)
 // #1      executeDefinitionMacro (package:_macros/src/executor/execute_macro.dart:167:21)
-// #2      MacroExpansionClient._executeDefinitionsPhase (package:_macros/src/executor/client.dart:276:17)
-// #3      MacroExpansionClient._handleMessage.<anonymous closure> (package:_macros/src/executor/client.dart:140:18)
+// #2      MacroExpansionClient._executeDefinitionsPhase.<anonymous closure> (package:_macros/src/executor/client.dart:276:17)
+// #3      runPhase.<anonymous closure> (package:_macros/src/executor/client.dart:385:30)
 // #4      _rootRun (dart:async/zone.dart:1399:13)
 // #5      _CustomZone.run (dart:async/zone.dart:1301:19)
-// #6      withRemoteInstanceZone (package:_macros/src/executor/remote_instance.dart:172:15)
-// #7      MacroExpansionClient._handleMessage (package:_macros/src/executor/client.dart:118:11)
-// #8      new MacroExpansionClient._.<anonymous closure> (package:_macros/src/executor/client.dart:37:39)
-// #9      _rootRunUnary (dart:async/zone.dart:1415:13)
-// #10     _CustomZone.runUnary (dart:async/zone.dart:1308:19)
-// #11     _CustomZone.runUnaryGuarded (dart:async/zone.dart:1217:7)
-// #12     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:365:11)
-// #13     _BufferingStreamSubscription._add (dart:async/stream_impl.dart:297:7)
-// #14     _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:784:19)
-// #15     _StreamController._add (dart:async/stream_controller.dart:658:7)
-// #16     _StreamController.add (dart:async/stream_controller.dart:606:5)
-// #17     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
+// #6      _CustomZone.runGuarded (dart:async/zone.dart:1209:7)
+// #7      runPhase (package:_macros/src/executor/client.dart:385:8)
+// #8      MacroExpansionClient._executeDefinitionsPhase (package:_macros/src/executor/client.dart:275:43)
+// #9      MacroExpansionClient._handleMessage.<anonymous closure> (package:_macros/src/executor/client.dart:140:18)
+// #10     _rootRun (dart:async/zone.dart:1399:13)
+// #11     _CustomZone.run (dart:async/zone.dart:1301:19)
+// #12     withRemoteInstanceZone (package:_macros/src/executor/remote_instance.dart:172:15)
+// #13     MacroExpansionClient._handleMessage (package:_macros/src/executor/client.dart:118:11)
+// #14     new MacroExpansionClient._.<anonymous closure> (package:_macros/src/executor/client.dart:37:39)
+// #15     _rootRunUnary (dart:async/zone.dart:1415:13)
+// #16     _CustomZone.runUnary (dart:async/zone.dart:1308:19)
+// #17     _CustomZone.runUnaryGuarded (dart:async/zone.dart:1217:7)
+// #18     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:365:11)
+// #19     _BufferingStreamSubscription._add (dart:async/stream_impl.dart:297:7)
+// #20     _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:784:19)
+// #21     _StreamController._add (dart:async/stream_controller.dart:658:7)
+// #22     _StreamController.add (dart:async/stream_controller.dart:606:5)
+// #23     _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)
 //
 // @CrashDefinitionMacro()
 //  ^
diff --git a/pkg/macros/CHANGELOG.md b/pkg/macros/CHANGELOG.md
index d5562b6..a37161c 100644
--- a/pkg/macros/CHANGELOG.md
+++ b/pkg/macros/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.1.2-main.1
+
+- Make it an error for macros to complete with pending async work scheduled.
+
 ## 0.1.2-main.0
 
 - Remove type parameter on internal `StaticType` implementation.
diff --git a/pkg/macros/pubspec.yaml b/pkg/macros/pubspec.yaml
index 6eef826..5271f4f 100644
--- a/pkg/macros/pubspec.yaml
+++ b/pkg/macros/pubspec.yaml
@@ -1,5 +1,5 @@
 name: macros
-version: 0.1.2-main.0
+version: 0.1.2-main.1
 description: >-
   This package is for macro authors, and exposes the APIs necessary to write
   a macro. It exports the APIs from the private `_macros` SDK vendored package.
@@ -11,4 +11,4 @@
 dependencies:
   _macros:
     sdk: dart
-    version: 0.3.0
+    version: 0.3.1