@@ -7,20 +7,23 @@ import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
77import 'package:analyzer/dart/ast/ast.dart' ;
88import 'package:analyzer/dart/ast/visitor.dart' ;
99import 'package:analyzer/dart/element/type.dart' ;
10+ import 'package:analyzer/dart/element/type_system.dart' ;
1011import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart' ;
1112import 'package:analyzer_plugin/utilities/fixes/fixes.dart' ;
1213
1314class AddAsync extends ResolvedCorrectionProducer {
1415 // TODO(pq): consider adding a variation that adds an `await` as well.
1516
16- /// A flag indicating whether this producer is producing a fix in the case
17- /// where a function is missing a return at the end.
18- final bool isForMissingReturn;
17+ final _Type _type;
1918
2019 /// Initialize a newly created producer.
21- AddAsync ({required super .context}) : isForMissingReturn = false ;
20+ AddAsync ({required super .context}) : _type = _Type .others;
21+
22+ AddAsync .missingReturn ({required super .context})
23+ : _type = _Type .missingReturn;
2224
23- AddAsync .missingReturn ({required super .context}) : isForMissingReturn = true ;
25+ AddAsync .wrongReturnType ({required super .context})
26+ : _type = _Type .wrongReturnType;
2427
2528 @override
2629 CorrectionApplicability get applicability =>
@@ -33,43 +36,100 @@ class AddAsync extends ResolvedCorrectionProducer {
3336
3437 @override
3538 Future <void > compute (ChangeBuilder builder) async {
36- if (isForMissingReturn) {
37- var node = this .node;
38- FunctionBody ? body;
39- DartType ? returnType;
40- switch (node) {
41- case FunctionDeclaration ():
42- body = node.functionExpression.body;
43- if (node.declaredFragment? .element case var declaredElement? ) {
44- returnType = declaredElement.returnType;
45- } else if (node.declaredFragment case var declaredFragment? ) {
46- returnType = declaredFragment.element.returnType;
47- }
48- case MethodDeclaration ():
49- body = node.body;
50- returnType = node.declaredFragment! .element.returnType;
51- }
52- if (body == null || returnType == null ) {
53- return ;
54- }
55- if (_isFutureVoid (returnType) && _hasNoReturns (body)) {
56- var final_body = body;
57- await builder.addDartFileEdit (file, (builder) {
58- builder.addSimpleInsertion (final_body.offset, 'async ' );
59- });
60- }
61- } else {
62- var body = node.thisOrAncestorOfType <FunctionBody >();
63- if (body != null && body.keyword == null ) {
64- await builder.addDartFileEdit (file, (builder) {
65- builder.convertFunctionFromSyncToAsync (
66- body: body,
67- typeSystem: typeSystem,
68- typeProvider: typeProvider,
69- );
70- });
71- }
39+ switch (_type) {
40+ case _Type .missingReturn:
41+ var node = this .node;
42+ FunctionBody ? body;
43+ DartType ? returnType;
44+ switch (node) {
45+ case FunctionDeclaration ():
46+ body = node.functionExpression.body;
47+ if (node.declaredFragment? .element case var declaredElement? ) {
48+ returnType = declaredElement.returnType;
49+ } else if (node.declaredFragment case var declaredFragment? ) {
50+ returnType = declaredFragment.element.returnType;
51+ }
52+ case MethodDeclaration ():
53+ body = node.body;
54+ returnType = node.declaredFragment! .element.returnType;
55+ }
56+ if (body == null || returnType == null ) {
57+ return ;
58+ }
59+ if (_isFutureVoid (returnType) && _hasNoReturns (body)) {
60+ var final_body = body;
61+ await builder.addDartFileEdit (file, (builder) {
62+ builder.addSimpleInsertion (final_body.offset, 'async ' );
63+ });
64+ }
65+ case _Type .wrongReturnType:
66+ var body = node.thisOrAncestorOfType <FunctionBody >();
67+ if (body == null ) {
68+ return ;
69+ }
70+ if (body.keyword != null ) {
71+ return ;
72+ }
73+ var expectedReturnType = _getStaticFunctionType (body);
74+ if (expectedReturnType == null ) {
75+ return ;
76+ }
77+
78+ if (expectedReturnType is ! InterfaceType ||
79+ ! expectedReturnType.isDartAsyncFuture) {
80+ return ;
81+ }
82+
83+ var visitor = _ReturnTypeTester (
84+ typeSystem,
85+ expectedReturnType.typeArguments.first,
86+ );
87+ if (visitor.returnsAreAssignable (body)) {
88+ await builder.addDartFileEdit (file, (builder) {
89+ builder.convertFunctionFromSyncToAsync (
90+ body: body,
91+ typeSystem: typeSystem,
92+ typeProvider: typeProvider,
93+ );
94+ });
95+ }
96+ case _Type .others:
97+ var body = node.thisOrAncestorOfType <FunctionBody >();
98+ if (body != null && body.keyword == null ) {
99+ await builder.addDartFileEdit (file, (builder) {
100+ builder.convertFunctionFromSyncToAsync (
101+ body: body,
102+ typeSystem: typeSystem,
103+ typeProvider: typeProvider,
104+ );
105+ });
106+ }
107+ }
108+ }
109+
110+ DartType ? _getStaticFunctionType (FunctionBody body) {
111+ // Gets return type for expression functions.
112+ if (body case ExpressionFunctionBody (: FunctionExpression parent)) {
113+ return parent.declaredFragment? .element.returnType;
114+ }
115+
116+ if (body is ! BlockFunctionBody ) {
117+ return null ;
118+ }
119+
120+ // Gets return type for methods.
121+ if (node.thisOrAncestorOfType <MethodDeclaration >() case var method?
122+ when method.body == body) {
123+ return method.declaredFragment? .element.returnType;
124+ }
125+
126+ // Gets return type for functions.
127+ if (node.thisOrAncestorOfType <FunctionDeclaration >() case var function?
128+ when function.functionExpression.body == body) {
129+ return function.declaredFragment? .element.returnType;
72130 }
131+
132+ return null ;
73133 }
74134
75135 /// Return `true` if there are no return statements in the given function
@@ -107,3 +167,83 @@ class _ReturnFinder extends RecursiveAstVisitor<void> {
107167 foundReturn = true ;
108168 }
109169}
170+
171+ /// An AST visitor used to test if all return statements in a function body
172+ /// are assignable to a given type.
173+ class _ReturnTypeTester extends RecursiveAstVisitor <void > {
174+ /// A flag indicating whether a return statement was visited.
175+ bool _foundOneReturn = false ;
176+
177+ /// A flag indicating whether the return type is assignable considering
178+ /// [isAssignable] .
179+ bool _returnsAreAssignable = true ;
180+
181+ /// The type system used to check assignability.
182+ final TypeSystem typeSystem;
183+
184+ /// The type that the return statements should be assignable to.
185+ final DartType futureOf;
186+
187+ /// Initialize a newly created visitor.
188+ _ReturnTypeTester (this .typeSystem, this .futureOf);
189+
190+ /// Tests whether a type is assignable to the [futureOf] type.
191+ bool isAssignable (DartType type) {
192+ if (typeSystem.isAssignableTo (type, futureOf)) {
193+ return true ;
194+ }
195+ if (type is InterfaceType && type.isDartAsyncFuture) {
196+ return typeSystem.isAssignableTo (type.typeArguments.first, futureOf);
197+ }
198+ return false ;
199+ }
200+
201+ /// Returns `true` if all return statements in the given [node] are
202+ /// assignable to the [futureOf] type.
203+ bool returnsAreAssignable (AstNode node) {
204+ _returnsAreAssignable = true ;
205+ _foundOneReturn = false ;
206+ node.accept (this );
207+ return _returnsAreAssignable && _foundOneReturn;
208+ }
209+
210+ @override
211+ void visitExpressionFunctionBody (ExpressionFunctionBody node) {
212+ _foundOneReturn = true ;
213+ if (node.expression.staticType case var type? ) {
214+ _returnsAreAssignable & = isAssignable (type);
215+ } else {
216+ _returnsAreAssignable = false ;
217+ }
218+ }
219+
220+ @override
221+ void visitFunctionExpression (FunctionExpression node) {
222+ // Return statements within closures aren't counted.
223+ }
224+
225+ @override
226+ void visitReturnStatement (ReturnStatement node) {
227+ _foundOneReturn = true ;
228+ if (node.expression? .staticType case var type? ) {
229+ _returnsAreAssignable & = isAssignable (type);
230+ } else {
231+ _returnsAreAssignable = false ;
232+ }
233+ }
234+ }
235+
236+ enum _Type {
237+ /// Indicates whether the producer is producing a fix in the case
238+ /// where a function has a return statement with the wrong return type.
239+ wrongReturnType,
240+
241+ /// Indicates whether the producer is producing a fix in the case
242+ /// where a function is missing a return at the end.
243+ missingReturn,
244+
245+ /// Indicates whether the producer is producing a fix that adds `async`
246+ /// to a function that is missing it. In cases where the error/lint is
247+ /// good enough to suggest adding `async` to a function, this is valid.
248+ others,
249+ }
0 commit comments