Migrator: Accept 'late final' hint; same as 'late' hint
`late final` is not functionally different from `late`, as far
as the migrator is concerned; it is just an indication of late
in the face of missing initialization.
Fixes https://github.com/dart-lang/sdk/issues/41655
Change-Id: I1b0efd3de3b5c7064784ebd1e86c7d12436b1319
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/167203
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/nnbd_migration/lib/nnbd_migration.dart b/pkg/nnbd_migration/lib/nnbd_migration.dart
index 787b235..b222f91 100644
--- a/pkg/nnbd_migration/lib/nnbd_migration.dart
+++ b/pkg/nnbd_migration/lib/nnbd_migration.dart
@@ -33,6 +33,12 @@
appliedMessage: 'Added a late keyword, due to assignment in `setUp`',
kind: NullabilityFixKind.addLateDueToTestSetup);
+ /// A variable declaration needs to be marked as "late" and "final" due to the
+ /// presence of a `/*late final*/` hint.
+ static const addLateFinalDueToHint = NullabilityFixDescription._(
+ appliedMessage: 'Added late and final keywords, due to a hint',
+ kind: NullabilityFixKind.addLateFinalDueToHint);
+
/// An expression's value needs to be null-checked.
static const checkExpression = NullabilityFixDescription._(
appliedMessage: 'Added a non-null assertion to nullable expression',
@@ -242,6 +248,7 @@
addLate,
addLateDueToHint,
addLateDueToTestSetup,
+ addLateFinalDueToHint,
addRequired,
addType,
checkExpression,
diff --git a/pkg/nnbd_migration/lib/src/fix_aggregator.dart b/pkg/nnbd_migration/lib/src/fix_aggregator.dart
index 47d8aa5..cc0a510 100644
--- a/pkg/nnbd_migration/lib/src/fix_aggregator.dart
+++ b/pkg/nnbd_migration/lib/src/fix_aggregator.dart
@@ -1017,9 +1017,11 @@
innerPlans.addAll(aggregator.innerPlansForNode(node));
var plan = aggregator.planner.passThrough(node, innerPlans: innerPlans);
if (lateHint != null) {
+ var description = lateHint.kind == HintCommentKind.late_
+ ? NullabilityFixDescription.addLateDueToHint
+ : NullabilityFixDescription.addLateFinalDueToHint;
plan = aggregator.planner.acceptLateHint(plan, lateHint,
- info: AtomicEditInfo(NullabilityFixDescription.addLateDueToHint, {},
- hintComment: lateHint));
+ info: AtomicEditInfo(description, {}, hintComment: lateHint));
}
return plan;
}
diff --git a/pkg/nnbd_migration/lib/src/front_end/info_builder.dart b/pkg/nnbd_migration/lib/src/front_end/info_builder.dart
index c16f646..d8b4f69 100644
--- a/pkg/nnbd_migration/lib/src/front_end/info_builder.dart
+++ b/pkg/nnbd_migration/lib/src/front_end/info_builder.dart
@@ -144,6 +144,9 @@
case NullabilityFixKind.addLateDueToHint:
edits.add(_removeHint('Remove /*late*/ hint'));
break;
+ case NullabilityFixKind.addLateFinalDueToHint:
+ edits.add(_removeHint('Remove /*late final*/ hint'));
+ break;
case NullabilityFixKind.addRequired:
// TODO(brianwilkerson) This doesn't verify that the meta package has
// been imported.
diff --git a/pkg/nnbd_migration/lib/src/front_end/migration_summary.dart b/pkg/nnbd_migration/lib/src/front_end/migration_summary.dart
index 1a90c12..bc4322f 100644
--- a/pkg/nnbd_migration/lib/src/front_end/migration_summary.dart
+++ b/pkg/nnbd_migration/lib/src/front_end/migration_summary.dart
@@ -69,6 +69,9 @@
case NullabilityFixKind.addLateDueToTestSetup:
return 'addLateDueToTestSetup';
break;
+ case NullabilityFixKind.addLateFinalDueToHint:
+ return 'addLateFinalDueToHint';
+ break;
case NullabilityFixKind.addRequired:
return 'addRequired';
break;
diff --git a/pkg/nnbd_migration/lib/src/front_end/unit_renderer.dart b/pkg/nnbd_migration/lib/src/front_end/unit_renderer.dart
index fbd5c8d..31e1bde 100644
--- a/pkg/nnbd_migration/lib/src/front_end/unit_renderer.dart
+++ b/pkg/nnbd_migration/lib/src/front_end/unit_renderer.dart
@@ -41,6 +41,7 @@
NullabilityFixKind.addLate,
NullabilityFixKind.addLateDueToTestSetup,
NullabilityFixKind.addLateDueToHint,
+ NullabilityFixKind.addLateFinalDueToHint,
NullabilityFixKind.checkExpressionDueToHint,
NullabilityFixKind.makeTypeNullableDueToHint,
NullabilityFixKind.removeLanguageVersionComment
@@ -265,6 +266,8 @@
return '$count late hint$s converted to late keyword$s';
case NullabilityFixKind.addLateDueToTestSetup:
return '$count late keyword$s added, due to assignment in `setUp`';
+ case NullabilityFixKind.addLateFinalDueToHint:
+ return '$count late final hint$s converted to late and final keywords';
case NullabilityFixKind.addRequired:
return '$count required keyword$s added';
case NullabilityFixKind.addType:
diff --git a/pkg/nnbd_migration/lib/src/node_builder.dart b/pkg/nnbd_migration/lib/src/node_builder.dart
index dd52503..0bdb629 100644
--- a/pkg/nnbd_migration/lib/src/node_builder.dart
+++ b/pkg/nnbd_migration/lib/src/node_builder.dart
@@ -633,6 +633,9 @@
if (hint != null && hint.kind == HintCommentKind.late_) {
_variables.recordLateHint(source, node, hint);
}
+ if (hint != null && hint.kind == HintCommentKind.lateFinal) {
+ _variables.recordLateHint(source, node, hint);
+ }
for (var variable in node.variables) {
variable.metadata.accept(this);
var declaredElement = variable.declaredElement;
diff --git a/pkg/nnbd_migration/lib/src/utilities/hint_utils.dart b/pkg/nnbd_migration/lib/src/utilities/hint_utils.dart
index 020cdf4..471149c 100644
--- a/pkg/nnbd_migration/lib/src/utilities/hint_utils.dart
+++ b/pkg/nnbd_migration/lib/src/utilities/hint_utils.dart
@@ -43,11 +43,11 @@
var lexeme = commentToken.lexeme;
if (lexeme.startsWith('/*') &&
lexeme.endsWith('*/') &&
- lexeme.length > 'late'.length) {
+ lexeme.length >= '/*late*/'.length) {
var commentText =
lexeme.substring('/*'.length, lexeme.length - '*/'.length).trim();
+ var commentOffset = commentToken.offset;
if (commentText == 'late') {
- var commentOffset = commentToken.offset;
var lateOffset = commentOffset + commentToken.lexeme.indexOf('late');
return HintComment(
HintCommentKind.late_,
@@ -57,6 +57,16 @@
lateOffset + 'late'.length,
commentToken.end,
token.offset);
+ } else if (commentText == 'late final') {
+ var lateOffset = commentOffset + commentToken.lexeme.indexOf('late');
+ return HintComment(
+ HintCommentKind.lateFinal,
+ commentOffset,
+ commentOffset,
+ lateOffset,
+ lateOffset + 'late final'.length,
+ commentToken.end,
+ token.offset);
}
}
}
@@ -196,4 +206,8 @@
/// The comment `/*late*/`, which indicates that the variable declaration
/// should be late.
late_,
+
+ /// The comment `/*late final*/`, which indicates that the variable
+ /// declaration should be late and final.
+ lateFinal,
}
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index 2140235..e137b2a 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -4070,6 +4070,28 @@
await _checkSingleFileChanges(content, expected);
}
+ Future<void> test_late_final_hint_instance_field_without_constructor() async {
+ var content = '''
+class C {
+ /*late final*/ int x;
+ f() {
+ x = 1;
+ }
+ int g() => x;
+}
+''';
+ var expected = '''
+class C {
+ late final int x;
+ f() {
+ x = 1;
+ }
+ int g() => x;
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
Future<void> test_late_hint_local_variable() async {
var content = '''
int f(bool b1, bool b2) {
@@ -4098,6 +4120,34 @@
await _checkSingleFileChanges(content, expected);
}
+ Future<void> test_late_final_hint_local_variable() async {
+ var content = '''
+int f(bool b1, bool b2) {
+ /*late final*/ int x;
+ if (b1) {
+ x = 1;
+ }
+ if (b2) {
+ return x;
+ }
+ return 0;
+}
+''';
+ var expected = '''
+int f(bool b1, bool b2) {
+ late final int x;
+ if (b1) {
+ x = 1;
+ }
+ if (b2) {
+ return x;
+ }
+ return 0;
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
Future<void> test_late_hint_static_field() async {
var content = '''
class C {
@@ -4138,6 +4188,24 @@
await _checkSingleFileChanges(content, expected);
}
+ Future<void> test_late_final_hint_top_level_var() async {
+ var content = '''
+/*late final*/ int x;
+f() {
+ x = 1;
+}
+int g() => x;
+''';
+ var expected = '''
+late final int x;
+f() {
+ x = 1;
+}
+int g() => x;
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
Future<void> test_leave_downcast_from_dynamic_implicit() async {
var content = 'int f(dynamic n) => n;';
var expected = 'int f(dynamic n) => n;';
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 8d7ad3c..9fc7d93 100644
--- a/pkg/nnbd_migration/test/front_end/info_builder_test.dart
+++ b/pkg/nnbd_migration/test/front_end/info_builder_test.dart
@@ -319,6 +319,28 @@
replacement: ''));
}
+ Future<void> test_addLateFinal_dueToHint() async {
+ var content = '/*late final*/ int x = 0;';
+ var migratedContent = '/*late final*/ int x = 0;';
+ var unit = await buildInfoForSingleTestFile(content,
+ migratedContent: migratedContent);
+ var regions = unit.fixRegions;
+ expect(regions, hasLength(2));
+ var textToRemove = '/*late final*/ ';
+ assertRegionPair(regions, 0,
+ offset1: migratedContent.indexOf('/*'),
+ length1: 2,
+ offset2: migratedContent.indexOf('*/'),
+ length2: 2,
+ explanation: 'Added late and final keywords, due to a hint',
+ kind: NullabilityFixKind.addLateFinalDueToHint,
+ edits: (List<EditDetail> edits) => assertEdit(
+ edit: edits.single,
+ offset: content.indexOf(textToRemove),
+ length: textToRemove.length,
+ replacement: ''));
+ }
+
Future<void> test_addLate_dueToTestSetup() async {
addTestCorePackage();
var content = '''
diff --git a/pkg/nnbd_migration/test/front_end/unit_renderer_test.dart b/pkg/nnbd_migration/test/front_end/unit_renderer_test.dart
index 2604c89..e7f297a 100644
--- a/pkg/nnbd_migration/test/front_end/unit_renderer_test.dart
+++ b/pkg/nnbd_migration/test/front_end/unit_renderer_test.dart
@@ -151,6 +151,16 @@
unorderedEquals(['1 late hint converted to late keyword']));
}
+ Future<void> test_editList_countsHintAcceptanceSingly_lateFinal() async {
+ await buildInfoForSingleTestFile('/*late final*/ int x = 0;',
+ migratedContent: '/*late final*/ int x = 0;');
+ var output = renderUnits()[0];
+ expect(
+ output.edits.keys,
+ unorderedEquals(
+ ['1 late final hint converted to late and final keywords']));
+ }
+
Future<void> test_editList_pluralHeader() async {
await buildInfoForSingleTestFile('''
int a = null;
diff --git a/pkg/nnbd_migration/test/node_builder_test.dart b/pkg/nnbd_migration/test/node_builder_test.dart
index 24737b9..b85ac69 100644
--- a/pkg/nnbd_migration/test/node_builder_test.dart
+++ b/pkg/nnbd_migration/test/node_builder_test.dart
@@ -2054,6 +2054,14 @@
isNotNull);
}
+ Future<void> test_variableDeclaration_late_final_hint_simple() async {
+ await analyze('/*late final*/ int i;');
+ expect(
+ variables.getLateHint(
+ testSource, findNode.variableDeclarationList('int i')),
+ isNotNull);
+ }
+
Future<void> test_variableDeclaration_late_hint_with_spaces() async {
await analyze('/* late */ int i;');
expect(