Skip to content

Commit d944b8b

Browse files
committed
[Refactoring] Use internal completion handler labels for async function's return type
If a completion handler specifies internal parameter labels, we can use those to label the elements of the tuple returned by the async alternative. For example ```swift func foo(completion: (_ first: String, _ second: String) -> Void) { } ``` gets refactored to ```swift func foo() async -> (first: String, second: String) { } ``` Resolves rdar://77268040
1 parent 38a8b00 commit d944b8b

File tree

2 files changed

+123
-10
lines changed

2 files changed

+123
-10
lines changed

lib/IDE/Refactoring.cpp

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4079,6 +4079,16 @@ class HandlerResult {
40794079
/// single parameter of `Result` type.
40804080
enum class HandlerType { INVALID, PARAMS, RESULT };
40814081

4082+
/// A single return type of a refactored async function. If the async function
4083+
/// returns a tuple, each element of the tuple (represented by a \c
4084+
/// LabeledReturnType) might have a label, otherwise the \p Label is empty.
4085+
struct LabeledReturnType {
4086+
Identifier Label;
4087+
swift::Type Ty;
4088+
4089+
LabeledReturnType(Identifier Label, swift::Type Ty) : Label(Label), Ty(Ty) {}
4090+
};
4091+
40824092
/// Given a function with an async alternative (or one that *could* have an
40834093
/// async alternative), stores information about the completion handler.
40844094
/// The completion handler can be either a variable (which includes a parameter)
@@ -4327,12 +4337,26 @@ struct AsyncHandlerDesc {
43274337
}
43284338
}
43294339

4340+
/// If the async function returns a tuple, the label of the \p Index -th
4341+
/// element in the returned tuple. If the function doesn't return a tuple or
4342+
/// the element is unlabeled, an empty identifier is returned.
4343+
Identifier getAsyncReturnTypeLabel(size_t Index) const {
4344+
assert(Index < getSuccessParams().size());
4345+
if (getSuccessParams().size() <= 1) {
4346+
// There can't be any labels if the async function doesn't return a tuple.
4347+
return Identifier();
4348+
} else {
4349+
return getSuccessParams()[Index].getInternalLabel();
4350+
}
4351+
}
4352+
43304353
/// Gets the return value types for the async equivalent of this handler.
4331-
ArrayRef<swift::Type>
4332-
getAsyncReturnTypes(SmallVectorImpl<swift::Type> &Scratch) const {
4333-
for (auto &Param : getSuccessParams()) {
4334-
auto Ty = Param.getParameterType();
4335-
Scratch.push_back(getSuccessParamAsyncReturnType(Ty));
4354+
ArrayRef<LabeledReturnType>
4355+
getAsyncReturnTypes(SmallVectorImpl<LabeledReturnType> &Scratch) const {
4356+
for (size_t I = 0; I < getSuccessParams().size(); ++I) {
4357+
auto Ty = getSuccessParams()[I].getParameterType();
4358+
Scratch.emplace_back(getAsyncReturnTypeLabel(I),
4359+
getSuccessParamAsyncReturnType(Ty));
43364360
}
43374361
return Scratch;
43384362
}
@@ -6370,7 +6394,7 @@ class AsyncConverter : private SourceEntityWalker {
63706394
return;
63716395
}
63726396

6373-
SmallVector<Type, 2> Scratch;
6397+
SmallVector<LabeledReturnType, 2> Scratch;
63746398
auto ReturnTypes = TopHandler.getAsyncReturnTypes(Scratch);
63756399
if (ReturnTypes.empty()) {
63766400
OS << " ";
@@ -6384,7 +6408,14 @@ class AsyncConverter : private SourceEntityWalker {
63846408
OS << "(";
63856409

63866410
llvm::interleave(
6387-
ReturnTypes, [&](Type Ty) { Ty->print(OS); }, [&]() { OS << ", "; });
6411+
ReturnTypes,
6412+
[&](LabeledReturnType TypeAndLabel) {
6413+
if (!TypeAndLabel.Label.empty()) {
6414+
OS << TypeAndLabel.Label << tok::colon << " ";
6415+
}
6416+
TypeAndLabel.Ty->print(OS);
6417+
},
6418+
[&]() { OS << ", "; });
63886419

63896420
if (ReturnTypes.size() > 1)
63906421
OS << ")";
@@ -7163,7 +7194,14 @@ class AsyncConverter : private SourceEntityWalker {
71637194
// completion(result.0, result.1)
71647195
// }
71657196
// }
7166-
OS << ResultName << tok::period << Index;
7197+
OS << ResultName << tok::period;
7198+
7199+
auto Label = HandlerDesc.getAsyncReturnTypeLabel(Index);
7200+
if (!Label.empty()) {
7201+
OS << Label;
7202+
} else {
7203+
OS << Index;
7204+
}
71677205
} else {
71687206
OS << ResultName;
71697207
}
@@ -7211,9 +7249,14 @@ class AsyncConverter : private SourceEntityWalker {
72117249
/// returned results via a completion handler described by \p HandlerDesc.
72127250
void addAsyncFuncReturnType(const AsyncHandlerDesc &HandlerDesc) {
72137251
// Type or (Type1, Type2, ...)
7214-
SmallVector<Type, 2> Scratch;
7252+
SmallVector<LabeledReturnType, 2> Scratch;
72157253
addTupleOf(HandlerDesc.getAsyncReturnTypes(Scratch), OS,
7216-
[&](auto Ty) { Ty->print(OS); });
7254+
[&](LabeledReturnType LabelAndType) {
7255+
if (!LabelAndType.Label.empty()) {
7256+
OS << LabelAndType.Label << tok::colon << " ";
7257+
}
7258+
LabelAndType.Ty->print(OS);
7259+
});
72177260
}
72187261

72197262
/// If \p FD is generic, adds a type annotation with the return type of the
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// RUN: %empty-directory(%t)
2+
3+
// RUN: %refactor-check-compiles -add-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=MULTIPLE-LABELED-RESULTS %s
4+
func mutlipleLabeledResults(completion: (_ first: String, _ second: String) -> Void) { }
5+
// MULTIPLE-LABELED-RESULTS: {
6+
// MULTIPLE-LABELED-RESULTS-NEXT: async {
7+
// MULTIPLE-LABELED-RESULTS-NEXT: let result = await mutlipleLabeledResults()
8+
// MULTIPLE-LABELED-RESULTS-NEXT: completion(result.first, result.second)
9+
// MULTIPLE-LABELED-RESULTS-NEXT: }
10+
// MULTIPLE-LABELED-RESULTS-NEXT: }
11+
// MULTIPLE-LABELED-RESULTS: func mutlipleLabeledResults() async -> (first: String, second: String) { }
12+
13+
// RUN: %refactor-check-compiles -add-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=MIXED-LABELED-RESULTS %s
14+
func mixedLabeledResult(completion: (_ first: String, String) -> Void) { }
15+
// MIXED-LABELED-RESULTS: {
16+
// MIXED-LABELED-RESULTS-NEXT: async {
17+
// MIXED-LABELED-RESULTS-NEXT: let result = await mixedLabeledResult()
18+
// MIXED-LABELED-RESULTS-NEXT: completion(result.first, result.1)
19+
// MIXED-LABELED-RESULTS-NEXT: }
20+
// MIXED-LABELED-RESULTS-NEXT: }
21+
// MIXED-LABELED-RESULTS: func mixedLabeledResult() async -> (first: String, String) { }
22+
23+
// RUN: %refactor-check-compiles -add-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=SINGLE-LABELED-RESULT %s
24+
func singleLabeledResult(completion: (_ first: String) -> Void) { }
25+
// SINGLE-LABELED-RESULT: {
26+
// SINGLE-LABELED-RESULT-NEXT: async {
27+
// SINGLE-LABELED-RESULT-NEXT: let result = await singleLabeledResult()
28+
// SINGLE-LABELED-RESULT-NEXT: completion(result)
29+
// SINGLE-LABELED-RESULT-NEXT: }
30+
// SINGLE-LABELED-RESULT-NEXT: }
31+
// SINGLE-LABELED-RESULT: func singleLabeledResult() async -> String { }
32+
33+
// RUN: %refactor-check-compiles -add-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=SINGLE-LABELED-RESULT-WITH-ERROR %s
34+
func singleLabeledResultWithError(completion: (_ first: String?, _ error: Error?) -> Void) { }
35+
// SINGLE-LABELED-RESULT-WITH-ERROR: {
36+
// SINGLE-LABELED-RESULT-WITH-ERROR-NEXT: async {
37+
// SINGLE-LABELED-RESULT-WITH-ERROR-NEXT: do {
38+
// SINGLE-LABELED-RESULT-WITH-ERROR-NEXT: let result = try await singleLabeledResultWithError()
39+
// SINGLE-LABELED-RESULT-WITH-ERROR-NEXT: completion(result, nil)
40+
// SINGLE-LABELED-RESULT-WITH-ERROR-NEXT: } catch {
41+
// SINGLE-LABELED-RESULT-WITH-ERROR-NEXT: completion(nil, error)
42+
// SINGLE-LABELED-RESULT-WITH-ERROR-NEXT: }
43+
// SINGLE-LABELED-RESULT-WITH-ERROR-NEXT: }
44+
// SINGLE-LABELED-RESULT-WITH-ERROR-NEXT: }
45+
// SINGLE-LABELED-RESULT-WITH-ERROR: func singleLabeledResultWithError() async throws -> String { }
46+
47+
// RUN: %refactor-check-compiles -add-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=MULTIPLE-LABELED-RESULT-WITH-ERROR %s
48+
func multipleLabeledResultWithError(completion: (_ first: String?, _ second: String?, _ error: Error?) -> Void) { }
49+
// MULTIPLE-LABELED-RESULT-WITH-ERROR: {
50+
// MULTIPLE-LABELED-RESULT-WITH-ERROR-NEXT: async {
51+
// MULTIPLE-LABELED-RESULT-WITH-ERROR-NEXT: do {
52+
// MULTIPLE-LABELED-RESULT-WITH-ERROR-NEXT: let result = try await multipleLabeledResultWithError()
53+
// MULTIPLE-LABELED-RESULT-WITH-ERROR-NEXT: completion(result.first, result.second, nil)
54+
// MULTIPLE-LABELED-RESULT-WITH-ERROR-NEXT: } catch {
55+
// MULTIPLE-LABELED-RESULT-WITH-ERROR-NEXT: completion(nil, nil, error)
56+
// MULTIPLE-LABELED-RESULT-WITH-ERROR-NEXT: }
57+
// MULTIPLE-LABELED-RESULT-WITH-ERROR-NEXT: }
58+
// MULTIPLE-LABELED-RESULT-WITH-ERROR-NEXT: }
59+
// MULTIPLE-LABELED-RESULT-WITH-ERROR: func multipleLabeledResultWithError() async throws -> (first: String, second: String) { }
60+
61+
func testConvertCall() {
62+
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=CONVERT-CALL %s
63+
mutlipleLabeledResults() { (a, b) in
64+
print(a)
65+
print(b)
66+
}
67+
// CONVERT-CALL: let (a, b) = await mutlipleLabeledResults()
68+
// CONVERT-CALL-NEXT: print(a)
69+
// CONVERT-CALL-NEXT: print(b)
70+
}

0 commit comments

Comments
 (0)