Migration: when removing dead code, remove unnecessay `??=`s

Fixes #38676.

Change-Id: Icc24c6f070d7f3e8a74fc9494aef3e4c2583fac4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/146320
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/nnbd_migration/lib/nnbd_migration.dart b/pkg/nnbd_migration/lib/nnbd_migration.dart
index 7388f0c..613c600 100644
--- a/pkg/nnbd_migration/lib/nnbd_migration.dart
+++ b/pkg/nnbd_migration/lib/nnbd_migration.dart
@@ -147,6 +147,12 @@
           'Changed a null-aware access into an ordinary access, because the target cannot be null',
       kind: NullabilityFixKind.removeDeadCode);
 
+  /// A null-aware assignment was removed because its LHS is non-nullable.
+  static const removeNullAwareAssignment = const NullabilityFixDescription._(
+      appliedMessage:
+          'Removed a null-aware assignment, because the target cannot be null',
+      kind: NullabilityFixKind.removeDeadCode);
+
   /// A message used to indicate a fix has been applied.
   final String appliedMessage;
 
diff --git a/pkg/nnbd_migration/lib/src/fix_aggregator.dart b/pkg/nnbd_migration/lib/src/fix_aggregator.dart
index 8576d1e..ba4452c 100644
--- a/pkg/nnbd_migration/lib/src/fix_aggregator.dart
+++ b/pkg/nnbd_migration/lib/src/fix_aggregator.dart
@@ -319,6 +319,12 @@
   NodeProducingEditPlan _apply(
       AssignmentExpression node, FixAggregator aggregator) {
     var lhsPlan = aggregator.planForNode(node.leftHandSide);
+    if (isWeakNullAware && !aggregator._warnOnWeakCode) {
+      // Just keep the LHS
+      return aggregator.planner.extract(node, lhsPlan as NodeProducingEditPlan,
+          infoAfter: AtomicEditInfo(
+              NullabilityFixDescription.removeNullAwareAssignment, const {}));
+    }
     var operatorPlan = _makeOperatorPlan(aggregator, node, node.operator);
     var rhsPlan = aggregator.planForNode(node.rightHandSide);
     var innerPlans = <EditPlan>[
@@ -335,6 +341,7 @@
     var operatorPlan = super._makeOperatorPlan(aggregator, node, operator);
     if (operatorPlan != null) return operatorPlan;
     if (isWeakNullAware) {
+      assert(aggregator._warnOnWeakCode);
       return aggregator.planner.informativeMessageForToken(node, operator,
           info: AtomicEditInfo(
               NullabilityFixDescription
diff --git a/pkg/nnbd_migration/test/fix_aggregator_test.dart b/pkg/nnbd_migration/test/fix_aggregator_test.dart
index f60af11..db9f2ba 100644
--- a/pkg/nnbd_migration/test/fix_aggregator_test.dart
+++ b/pkg/nnbd_migration/test/fix_aggregator_test.dart
@@ -144,6 +144,16 @@
     expect(edit.length, '??='.length);
   }
 
+  Future<void> test_assignment_weak_null_aware_remove() async {
+    var content = 'f(int x, int y) => x ??= y;';
+    await analyze(content);
+    var previewInfo = run({
+      findNode.assignment('??='): NodeChangeForAssignment()
+        ..isWeakNullAware = true
+    }, warnOnWeakCode: false);
+    expect(previewInfo.applyTo(code), 'f(int x, int y) => x;');
+  }
+
   Future<void> test_eliminateDeadIf_changesInKeptCode() async {
     await analyze('''
 f(int i, int/*?*/ j) {
diff --git a/pkg/nnbd_migration/test/front_end/info_builder_test.dart b/pkg/nnbd_migration/test/front_end/info_builder_test.dart
index 8e84aaf..a1490f0 100644
--- a/pkg/nnbd_migration/test/front_end/info_builder_test.dart
+++ b/pkg/nnbd_migration/test/front_end/info_builder_test.dart
@@ -834,6 +834,45 @@
         kind: NullabilityFixKind.checkExpression);
   }
 
+  void test_nullAwareAssignment_remove() async {
+    var unit = await buildInfoForSingleTestFile('''
+int f(int/*!*/ x, int y) => x ??= y;
+''', migratedContent: '''
+int  f(int/*!*/ x, int  y) => x ??= y;
+''', warnOnWeakCode: false, removeViaComments: false);
+    var codeToRemove = ' ??= y';
+    var removalOffset = unit.content.indexOf(codeToRemove);
+    var region =
+        unit.regions.where((region) => region.offset == removalOffset).single;
+    assertRegion(
+        region: region,
+        length: codeToRemove.length,
+        explanation:
+            'Removed a null-aware assignment, because the target cannot be '
+            'null',
+        kind: NullabilityFixKind.removeDeadCode,
+        edits: isEmpty);
+  }
+
+  void test_nullAwareAssignment_unnecessaryInStrongMode() async {
+    var unit = await buildInfoForSingleTestFile('''
+int f(int/*!*/ x, int y) => x ??= y;
+''', migratedContent: '''
+int  f(int/*!*/ x, int  y) => x ??= y;
+''', warnOnWeakCode: true);
+    var operator = '??=';
+    var operatorOffset = unit.content.indexOf(operator);
+    var region =
+        unit.regions.where((region) => region.offset == operatorOffset).single;
+    assertRegion(
+        region: region,
+        length: operator.length,
+        explanation:
+            'Null-aware assignment will be unnecessary in strong checking mode',
+        kind: NullabilityFixKind.nullAwareAssignmentUnnecessaryInStrongMode,
+        edits: isEmpty);
+  }
+
   void test_nullAwarenessUnnecessaryInStrongMode() async {
     var unit = await buildInfoForSingleTestFile('''
 int f(String s) => s?.length;