Migration: handle for-each loops where the loop variable is not local.

Fixes #44071.

Bug: https://github.com/dart-lang/sdk/issues/44071
Change-Id: I6a1a9df22f70086860c36108e529f189d3d24875
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/174565
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index 7003df0..b348db7 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -2659,21 +2659,23 @@
           node is Statement ? node : null, parts.condition);
     } else if (parts is ForEachParts) {
       Element lhsElement;
+      DecoratedType lhsType;
       if (parts is ForEachPartsWithDeclaration) {
         var variableElement = parts.loopVariable.declaredElement;
         _flowAnalysis.declare(variableElement, true);
         lhsElement = variableElement;
         _dispatch(parts.loopVariable?.type);
+        lhsType = _variables.decoratedElementType(lhsElement);
       } else if (parts is ForEachPartsWithIdentifier) {
         lhsElement = parts.identifier.staticElement;
+        lhsType = _dispatch(parts.identifier);
       } else {
         throw StateError(
             'Unexpected ForEachParts subtype: ${parts.runtimeType}');
       }
       var iterableType = _checkExpressionNotNull(parts.iterable);
       DecoratedType elementType;
-      if (lhsElement != null) {
-        DecoratedType lhsType = _variables.decoratedElementType(lhsElement);
+      if (lhsType != null) {
         var iterableTypeType = iterableType.type;
         if (_typeSystem.isSubtypeOf(
             iterableTypeType, typeProvider.iterableDynamicType)) {
diff --git a/pkg/nnbd_migration/lib/src/fix_builder.dart b/pkg/nnbd_migration/lib/src/fix_builder.dart
index ae77229..1d4748f 100644
--- a/pkg/nnbd_migration/lib/src/fix_builder.dart
+++ b/pkg/nnbd_migration/lib/src/fix_builder.dart
@@ -464,6 +464,15 @@
           // correct post-migration type.
           return variable.typeInternal;
         }
+        if (variable is ParameterElement) {
+          var enclosingElement = variable.enclosingElement;
+          if (enclosingElement is PropertyAccessorElement &&
+              enclosingElement.isSynthetic) {
+            // This is the parameter of a synthetic getter, so it has the same
+            // type as the corresponding variable.
+            return _fixBuilder._computeMigratedType(enclosingElement.variable);
+          }
+        }
         return _fixBuilder._computeMigratedType(variable);
       });
 
diff --git a/pkg/nnbd_migration/lib/src/front_end/non_nullable_fix.dart b/pkg/nnbd_migration/lib/src/front_end/non_nullable_fix.dart
index 136711e..6ec0a2e 100644
--- a/pkg/nnbd_migration/lib/src/front_end/non_nullable_fix.dart
+++ b/pkg/nnbd_migration/lib/src/front_end/non_nullable_fix.dart
@@ -510,7 +510,7 @@
   void reportException(
       Source source, AstNode node, Object exception, StackTrace stackTrace) {
     listener.client.onException('''
-$exception
+$exception at offset ${node.offset} in $source ($node)
 
 $stackTrace''');
   }
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index f36f2b2..8e1285fa 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -4678,6 +4678,54 @@
     await _checkSingleFileChanges(content, expected);
   }
 
+  Future<void> test_loop_var_is_field() async {
+    var content = '''
+class C {
+  int x;
+  C(this.x);
+  f(List<int/*?*/> y) {
+    for (x in y) {}
+  }
+}
+''';
+    var expected = '''
+class C {
+  int? x;
+  C(this.x);
+  f(List<int?> y) {
+    for (x in y) {}
+  }
+}
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
+  Future<void> test_loop_var_is_inherited_field_with_substitution() async {
+    var content = '''
+class B<T> {
+  T x;
+  B(this.x);
+}
+abstract class C implements B<int> {
+  f(List<int/*?*/> y) {
+    for (x in y) {}
+  }
+}
+''';
+    var expected = '''
+class B<T> {
+  T x;
+  B(this.x);
+}
+abstract class C implements B<int?> {
+  f(List<int?> y) {
+    for (x in y) {}
+  }
+}
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
   Future<void> test_make_downcast_explicit() async {
     var content = 'int f(num n) => n;';
     var expected = 'int f(num n) => n as int;';