Add a quick fix to return_of_invalid_type

Bug: 46785
Change-Id: Ided0ebd07935289b9b7fdfaaf5a38a992646c9d4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/209962
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
This commit is contained in:
Ahmed Ashour
2021-08-23 23:56:07 +00:00
committed by commit-bot@chromium.org
parent 5502e51627
commit f4b6fa85ea
5 changed files with 355 additions and 0 deletions
@@ -0,0 +1,111 @@
// Copyright (c) 2021, 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/ast/ast.dart';
import 'package:analyzer/dart/ast/syntactic_entity.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
import 'package:analyzer_plugin/utilities/range_factory.dart';
class ReplaceReturnType extends CorrectionProducer {
String _newType = '';
@override
List<Object> get fixArguments => [_newType];
@override
FixKind get fixKind => DartFixKind.REPLACE_RETURN_TYPE;
@override
Future<void> compute(ChangeBuilder builder) async {
final node = this.node;
if (node is Expression) {
final typeSystem = libraryElement.typeSystem;
var newType = node.staticType;
void updateNewType(SyntacticEntity entity) {
if (entity is FunctionExpression) {
return;
} else if (entity is ReturnStatement) {
var type = entity.expression?.staticType;
if (type != null) {
if (newType == null) {
newType = type;
} else {
newType = typeSystem.leastUpperBound(newType!, type);
}
}
} else if (entity is AstNode) {
entity.childEntities.forEach(updateNewType);
}
}
var functionBody = node.thisOrAncestorOfType<FunctionBody>();
var parent = functionBody?.parent;
var grandParent = parent?.parent;
TypeAnnotation? returnType;
if (grandParent is FunctionDeclaration) {
updateNewType(grandParent.functionExpression.body);
returnType = grandParent.returnType;
} else if (parent is MethodDeclaration) {
updateNewType(parent.body);
if (_isCompatibleWithReturnType(parent, newType)) {
returnType = parent.returnType;
}
}
if (returnType != null && newType != null) {
if (functionBody!.isAsynchronous) {
newType = typeProvider.futureType(newType!);
}
_newType = newType!.getDisplayString(withNullability: true);
await builder.addDartFileEdit(file, (builder) {
if (builder.canWriteType(newType)) {
builder.addReplacement(range.node(returnType!), (builder) {
builder.writeType(newType);
});
}
});
}
}
}
bool _isCompatibleWithReturnType(
MethodDeclaration method, DartType? newType) {
if (newType != null) {
var clazz = method.thisOrAncestorOfType<ClassDeclaration>();
if (clazz != null) {
var classElement = clazz.declaredElement!;
var overriddenList = InheritanceManager3().getOverridden2(
classElement,
Name(
classElement.library.source.uri,
method.declaredElement!.name,
));
if (overriddenList != null) {
var notSubtype = overriddenList.any((element) => !libraryElement
.typeSystem
.isSubtypeOf(newType, element.returnType));
if (notSubtype) {
return false;
}
}
}
return true;
}
return false;
}
/// Return an instance of this class. Used as a tear-off in `FixProcessor`.
static ReplaceReturnType newInstance() => ReplaceReturnType();
}
@@ -745,6 +745,10 @@ class DartFixKind {
'dart.fix.replace.nullWithVoid.multi',
DartFixKindPriority.DEFAULT,
"Replace 'Null' with 'void' everywhere in file");
static const REPLACE_RETURN_TYPE = FixKind(
'dart.fix.replace.returnType',
DartFixKindPriority.DEFAULT,
"Replace the return type with '{0}'");
static const REPLACE_RETURN_TYPE_FUTURE = FixKind(
'dart.fix.replace.returnTypeFuture',
DartFixKindPriority.DEFAULT,
@@ -140,6 +140,7 @@ import 'package:analysis_server/src/services/correction/dart/replace_final_with_
import 'package:analysis_server/src/services/correction/dart/replace_final_with_var.dart';
import 'package:analysis_server/src/services/correction/dart/replace_new_with_const.dart';
import 'package:analysis_server/src/services/correction/dart/replace_null_with_closure.dart';
import 'package:analysis_server/src/services/correction/dart/replace_return_type.dart';
import 'package:analysis_server/src/services/correction/dart/replace_return_type_future.dart';
import 'package:analysis_server/src/services/correction/dart/replace_var_with_dynamic.dart';
import 'package:analysis_server/src/services/correction/dart/replace_with_brackets.dart';
@@ -919,9 +920,11 @@ class FixProcessor extends BaseProcessor {
],
CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION: [
MakeReturnTypeNullable.newInstance,
ReplaceReturnType.newInstance,
],
CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_METHOD: [
MakeReturnTypeNullable.newInstance,
ReplaceReturnType.newInstance,
],
CompileTimeErrorCode.TYPE_TEST_WITH_UNDEFINED_NAME: [
ChangeTo.classOrMixin,
@@ -0,0 +1,235 @@
// Copyright (c) 2021, 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/error/codes.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(ReplaceReturnTypeTest);
});
}
@reflectiveTest
class ReplaceReturnTypeTest extends FixProcessorTest {
@override
FixKind get kind => DartFixKind.REPLACE_RETURN_TYPE;
Future<void> test_async_method() async {
await resolveTestCode('''
class A {
Future<int> m() async {
return '';
}
}
''');
await assertHasFix('''
class A {
Future<String> m() async {
return '';
}
}
''');
}
Future<void> test_closure() async {
await resolveTestCode('''
class A {
int m() {
var list = <String>[];
list.map((e) {
return 0;
});
return 2.4;
}
}
''');
await assertHasFix('''
class A {
double m() {
var list = <String>[];
list.map((e) {
return 0;
});
return 2.4;
}
}
''');
}
Future<void> test_function() async {
await resolveTestCode('''
int f() {
return '';
}
''');
await assertHasFix('''
String f() {
return '';
}
''');
}
Future<void> test_function_local() async {
await resolveTestCode('''
void top() {
int f() {
return '';
}
}
''');
await assertHasFix('''
void top() {
String f() {
return '';
}
}
''', errorFilter: (error) {
return error.errorCode == CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION;
});
}
Future<void> test_method() async {
await resolveTestCode('''
class A {
int m() {
return '';
}
}
''');
await assertHasFix('''
class A {
String m() {
return '';
}
}
''');
}
Future<void> test_methodOverride() async {
await resolveTestCode('''
class A {
A m() => this;
}
class B extends A {
@override
int m() => this;
}
''');
await assertHasFix('''
class A {
A m() => this;
}
class B extends A {
@override
B m() => this;
}
''', errorFilter: (error) {
return error.errorCode == CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_METHOD;
});
}
Future<void> test_methodOverride_multiple_subtype() async {
await resolveTestCode('''
class A {}
class B extends A {}
class Parent {
A m() => A();
}
class I {
B m() => B();
}
class D extends Parent implements I {
@override
B m() => A();
}
''');
await assertNoFix();
}
Future<void> test_methodOverride_subtype() async {
await resolveTestCode('''
class A {
B m() => B();
}
class B extends A {
@override
B m() => A();
}
''');
await assertNoFix();
}
Future<void> test_privateType() async {
addSource('/home/test/lib/a.dart', '''
class A {
_B b => _B();
}
class _B {}
''');
await resolveTestCode('''
import 'package:test/a.dart';
int f(A a) {
return a.b();
}
''');
await assertNoFix();
}
Future<void> test_upperBound_function() async {
await resolveTestCode('''
int f() {
if (true) {
return 3;
}
return 2.4;
}
''');
await assertHasFix('''
num f() {
if (true) {
return 3;
}
return 2.4;
}
''', errorFilter: (error) {
return error.errorCode == CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_FUNCTION;
});
}
Future<void> test_upperBound_method() async {
await resolveTestCode('''
class A {
int m() {
if (true) {
return 3;
}
return 2.4;
}
}
''');
await assertHasFix('''
class A {
num m() {
if (true) {
return 3;
}
return 2.4;
}
}
''', errorFilter: (error) {
return error.errorCode == CompileTimeErrorCode.RETURN_OF_INVALID_TYPE_FROM_METHOD;
});
}
}
@@ -167,6 +167,7 @@ import 'replace_final_with_var_test.dart' as replace_final_with_var;
import 'replace_new_with_const_test.dart' as replace_new_with_const;
import 'replace_null_with_closure_test.dart' as replace_null_with_closure;
import 'replace_return_type_future_test.dart' as replace_return_type_future;
import 'replace_return_type_test.dart' as replace_return_type;
import 'replace_var_with_dynamic_test.dart' as replace_var_with_dynamic;
import 'replace_with_brackets_test.dart' as replace_with_brackets;
import 'replace_with_conditional_assignment_test.dart'
@@ -339,6 +340,7 @@ void main() {
replace_new_with_const.main();
replace_null_with_closure.main();
replace_null_with_void.main();
replace_return_type.main();
replace_return_type_future.main();
replace_var_with_dynamic.main();
replace_with_brackets.main();