Add a quick fix to make a return type nullable

Change-Id: I6e400a5d46f9725586cb5f24601013abf7b3ebb5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/157603
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/make_return_type_nullable.dart b/pkg/analysis_server/lib/src/services/correction/dart/make_return_type_nullable.dart
new file mode 100644
index 0000000..5fdea07
--- /dev/null
+++ b/pkg/analysis_server/lib/src/services/correction/dart/make_return_type_nullable.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analysis_server/src/services/correction/dart/abstract_producer.dart';
+import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer/dart/analysis/features.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+
+class MakeReturnTypeNullable extends CorrectionProducer {
+  @override
+  FixKind get fixKind => DartFixKind.MAKE_RETURN_TYPE_NULLABLE;
+
+  @override
+  Future<void> compute(ChangeBuilder builder) async {
+    if (!unit.featureSet.isEnabled(Feature.non_nullable)) {
+      return;
+    }
+    if (node is! Expression) {
+      return;
+    }
+    var body = node.thisOrAncestorOfType<FunctionBody>();
+    TypeAnnotation returnType;
+    var function = body.parent;
+    if (function is FunctionExpression) {
+      function = function.parent;
+    }
+    if (function is MethodDeclaration) {
+      returnType = function.returnType;
+    } else if (function is FunctionDeclaration) {
+      returnType = function.returnType;
+    } else {
+      return;
+    }
+    if (body.isAsynchronous || body.isGenerator) {
+      if (returnType is! NamedType) {
+        return null;
+      }
+      var typeArguments = (returnType as NamedType).typeArguments;
+      if (typeArguments == null) {
+        return null;
+      }
+      var arguments = typeArguments.arguments;
+      if (arguments.length != 1) {
+        return null;
+      }
+      returnType = arguments[0];
+    }
+    if (node is! NullLiteral &&
+        !typeSystem.isAssignableTo(returnType.type,
+            typeSystem.promoteToNonNull((node as Expression).staticType))) {
+      return;
+    }
+    await builder.addDartFileEdit(file, (builder) {
+      builder.addSimpleInsertion(returnType.end, '?');
+    });
+  }
+
+  /// Return an instance of this class. Used as a tear-off in `FixProcessor`.
+  static MakeReturnTypeNullable newInstance() => MakeReturnTypeNullable();
+}
diff --git a/pkg/analysis_server/lib/src/services/correction/fix.dart b/pkg/analysis_server/lib/src/services/correction/fix.dart
index 464a32d..0f67130 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix.dart
@@ -315,6 +315,8 @@
   static const MAKE_FIELD_NOT_FINAL =
       FixKind('dart.fix.makeFieldNotFinal', 50, "Make field '{0}' not final");
   static const MAKE_FINAL = FixKind('dart.fix.makeFinal', 50, 'Make final');
+  static const MAKE_RETURN_TYPE_NULLABLE = FixKind(
+      'dart.fix.makeReturnTypeNullable', 50, 'Make the return type nullable');
   static const MOVE_TYPE_ARGUMENTS_TO_CLASS = FixKind(
       'dart.fix.moveTypeArgumentsToClass',
       50,
diff --git a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
index 0bf5738..ec4ab2e 100644
--- a/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
+++ b/pkg/analysis_server/lib/src/services/correction/fix_internal.dart
@@ -79,6 +79,7 @@
 import 'package:analysis_server/src/services/correction/dart/make_class_abstract.dart';
 import 'package:analysis_server/src/services/correction/dart/make_field_not_final.dart';
 import 'package:analysis_server/src/services/correction/dart/make_final.dart';
+import 'package:analysis_server/src/services/correction/dart/make_return_type_nullable.dart';
 import 'package:analysis_server/src/services/correction/dart/make_variable_not_final.dart';
 import 'package:analysis_server/src/services/correction/dart/move_type_arguments_to_class.dart';
 import 'package:analysis_server/src/services/correction/dart/organize_imports.dart';
@@ -751,6 +752,12 @@
     CompileTimeErrorCode.NULLABLE_TYPE_IN_WITH_CLAUSE: [
       RemoveQuestionMark.newInstance,
     ],
+    CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION: [
+      MakeReturnTypeNullable.newInstance,
+    ],
+    CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_METHOD: [
+      MakeReturnTypeNullable.newInstance,
+    ],
     CompileTimeErrorCode.TYPE_TEST_WITH_UNDEFINED_NAME: [
       ChangeTo.classOrMixin,
       CreateClass.newInstance,
@@ -843,6 +850,9 @@
       MoveTypeArgumentsToClass.newInstance,
       RemoveTypeArguments.newInstance,
     ],
+    CompileTimeErrorCode.YIELD_OF_INVALID_TYPE: [
+      MakeReturnTypeNullable.newInstance,
+    ],
 
     HintCode.CAN_BE_NULL_AFTER_NULL_AWARE: [
       ReplaceWithNullAware.newInstance,
diff --git a/pkg/analysis_server/test/src/services/correction/fix/make_return_type_nullable_test.dart b/pkg/analysis_server/test/src/services/correction/fix/make_return_type_nullable_test.dart
new file mode 100644
index 0000000..6181ed4
--- /dev/null
+++ b/pkg/analysis_server/test/src/services/correction/fix/make_return_type_nullable_test.dart
@@ -0,0 +1,150 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analysis_server/src/services/correction/fix.dart';
+import 'package:analyzer/src/dart/analysis/experiments.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'fix_processor.dart';
+
+void main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(MakeReturnTypeNullableTest);
+  });
+}
+
+@reflectiveTest
+class MakeReturnTypeNullableTest extends FixProcessorTest {
+  @override
+  List<String> get experiments => [EnableString.non_nullable];
+
+  @override
+  FixKind get kind => DartFixKind.MAKE_RETURN_TYPE_NULLABLE;
+
+  Future<void> test_function_async() async {
+    await resolveTestUnit('''
+Future<String> f(String? s) async {
+  return s;
+}
+''');
+    await assertHasFix('''
+Future<String?> f(String? s) async {
+  return s;
+}
+''');
+  }
+
+  Future<void> test_function_asyncStar() async {
+    await resolveTestUnit('''
+Stream<String> f(String? s) async* {
+  yield s;
+}
+''');
+    await assertHasFix('''
+Stream<String?> f(String? s) async* {
+  yield s;
+}
+''');
+  }
+
+  Future<void> test_function_sync() async {
+    await resolveTestUnit('''
+String f(String? s) {
+  return s;
+}
+''');
+    await assertHasFix('''
+String? f(String? s) {
+  return s;
+}
+''');
+  }
+
+  Future<void> test_function_syncStar() async {
+    await resolveTestUnit('''
+Iterable<String> f(String? s) sync* {
+  yield s;
+}
+''');
+    await assertHasFix('''
+Iterable<String?> f(String? s) sync* {
+  yield s;
+}
+''');
+  }
+
+  Future<void> test_getter_sync() async {
+    await resolveTestUnit('''
+class C {
+  String? f;
+  String get g => f;
+}
+''');
+    await assertHasFix('''
+class C {
+  String? f;
+  String? get g => f;
+}
+''');
+  }
+
+  Future<void> test_incompatibilityIsNotLimitedToNullability() async {
+    await resolveTestUnit('''
+int f() {
+  return '';
+}
+''');
+    await assertNoFix();
+  }
+
+  Future<void> test_localFunction_sync() async {
+    await resolveTestUnit('''
+void f() {
+  String g(String? s) {
+    return s;
+  }
+  g(null);
+}
+''');
+    await assertHasFix('''
+void f() {
+  String? g(String? s) {
+    return s;
+  }
+  g(null);
+}
+''');
+  }
+
+  Future<void> test_method_sync() async {
+    await resolveTestUnit('''
+class C {
+  String m(String? s) {
+    return s;
+  }
+}
+''');
+    await assertHasFix('''
+class C {
+  String? m(String? s) {
+    return s;
+  }
+}
+''');
+  }
+
+  Future<void> test_returnTypeHasTypeArguments() async {
+    await resolveTestUnit('''
+List<String> f() {
+  return null;
+}
+''');
+    await assertHasFix('''
+List<String>? f() {
+  return null;
+}
+''');
+  }
+}
diff --git a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
index bb9234d..64dde02 100644
--- a/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
+++ b/pkg/analysis_server/test/src/services/correction/fix/test_all.dart
@@ -92,6 +92,7 @@
 import 'make_class_abstract_test.dart' as make_class_abstract;
 import 'make_field_not_final_test.dart' as make_field_not_final;
 import 'make_final_test.dart' as make_final;
+import 'make_return_type_nullable_test.dart' as make_return_type_nullable;
 import 'make_variable_not_final_test.dart' as make_variable_not_final;
 import 'move_type_arguments_to_class_test.dart' as move_type_arguments_to_class;
 import 'organize_imports_test.dart' as organize_imports;
@@ -246,6 +247,7 @@
     make_class_abstract.main();
     make_field_not_final.main();
     make_final.main();
+    make_return_type_nullable.main();
     make_variable_not_final.main();
     move_type_arguments_to_class.main();
     organize_imports.main();