Skip to content

Commit 4b462dd

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 d572e13 commit 4b462dd

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
@@ -4088,6 +4088,16 @@ class HandlerResult {
40884088
/// single parameter of `Result` type.
40894089
enum class HandlerType { INVALID, PARAMS, RESULT };
40904090

4091+
/// A single return type of a refactored async function. If the async function
4092+
/// returns a tuple, each element of the tuple (represented by a \c
4093+
/// LabeledReturnType) might have a label, otherwise the \p Label is empty.
4094+
struct LabeledReturnType {
4095+
Identifier Label;
4096+
swift::Type Ty;
4097+
4098+
LabeledReturnType(Identifier Label, swift::Type Ty) : Label(Label), Ty(Ty) {}
4099+
};
4100+
40914101
/// Given a function with an async alternative (or one that *could* have an
40924102
/// async alternative), stores information about the completion handler.
40934103
/// The completion handler can be either a variable (which includes a parameter)
@@ -4336,12 +4346,26 @@ struct AsyncHandlerDesc {
43364346
}
43374347
}
43384348

4349+
/// If the async function returns a tuple, the label of the \p Index -th
4350+
/// element in the returned tuple. If the function doesn't return a tuple or
4351+
/// the element is unlabeled, an empty identifier is returned.
4352+
Identifier getAsyncReturnTypeLabel(size_t Index) const {
4353+
assert(Index < getSuccessParams().size());
4354+
if (getSuccessParams().size() <= 1) {
4355+
// There can't be any labels if the async function doesn't return a tuple.
4356+
return Identifier();
4357+
} else {
4358+
return getSuccessParams()[Index].getInternalLabel();
4359+
}
4360+
}
4361+
43394362
/// Gets the return value types for the async equivalent of this handler.
4340-
ArrayRef<swift::Type>
4341-
getAsyncReturnTypes(SmallVectorImpl<swift::Type> &Scratch) const {
4342-
for (auto &Param : getSuccessParams()) {
4343-
auto Ty = Param.getParameterType();
4344-
Scratch.push_back(getSuccessParamAsyncReturnType(Ty));
4363+
ArrayRef<LabeledReturnType>
4364+
getAsyncReturnTypes(SmallVectorImpl<LabeledReturnType> &Scratch) const {
4365+
for (size_t I = 0; I < getSuccessParams().size(); ++I) {
4366+
auto Ty = getSuccessParams()[I].getParameterType();
4367+
Scratch.emplace_back(getAsyncReturnTypeLabel(I),
4368+
getSuccessParamAsyncReturnType(Ty));
43454369
}
43464370
return Scratch;
43474371
}
@@ -6387,7 +6411,7 @@ class AsyncConverter : private SourceEntityWalker {
63876411
return;
63886412
}
63896413

6390-
SmallVector<Type, 2> Scratch;
6414+
SmallVector<LabeledReturnType, 2> Scratch;
63916415
auto ReturnTypes = TopHandler.getAsyncReturnTypes(Scratch);
63926416
if (ReturnTypes.empty()) {
63936417
OS << " ";
@@ -6401,7 +6425,14 @@ class AsyncConverter : private SourceEntityWalker {
64016425
OS << "(";
64026426

64036427
llvm::interleave(
6404-
ReturnTypes, [&](Type Ty) { Ty->print(OS); }, [&]() { OS << ", "; });
6428+
ReturnTypes,
6429+
[&](LabeledReturnType TypeAndLabel) {
6430+
if (!TypeAndLabel.Label.empty()) {
6431+
OS << TypeAndLabel.Label << tok::colon << " ";
6432+
}
6433+
TypeAndLabel.Ty->print(OS);
6434+
},
6435+
[&]() { OS << ", "; });
64056436

64066437
if (ReturnTypes.size() > 1)
64076438
OS << ")";
@@ -7180,7 +7211,14 @@ class AsyncConverter : private SourceEntityWalker {
71807211
// completion(result.0, result.1)
71817212
// }
71827213
// }
7183-
OS << ResultName << tok::period << Index;
7214+
OS << ResultName << tok::period;
7215+
7216+
auto Label = HandlerDesc.getAsyncReturnTypeLabel(Index);
7217+
if (!Label.empty()) {
7218+
OS << Label;
7219+
} else {
7220+
OS << Index;
7221+
}
71847222
} else {
71857223
OS << ResultName;
71867224
}
@@ -7228,9 +7266,14 @@ class AsyncConverter : private SourceEntityWalker {
72287266
/// returned results via a completion handler described by \p HandlerDesc.
72297267
void addAsyncFuncReturnType(const AsyncHandlerDesc &HandlerDesc) {
72307268
// Type or (Type1, Type2, ...)
7231-
SmallVector<Type, 2> Scratch;
7269+
SmallVector<LabeledReturnType, 2> Scratch;
72327270
addTupleOf(HandlerDesc.getAsyncReturnTypes(Scratch), OS,
7233-
[&](auto Ty) { Ty->print(OS); });
7271+
[&](LabeledReturnType LabelAndType) {
7272+
if (!LabelAndType.Label.empty()) {
7273+
OS << LabelAndType.Label << tok::colon << " ";
7274+
}
7275+
LabelAndType.Ty->print(OS);
7276+
});
72347277
}
72357278

72367279
/// 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)