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;';