Skip to content

Commit 69d251a

Browse files
authored
Merge pull request #35929 from bnbarham/async-refactoring-bugs
Fix various async refactoring bugs
2 parents 3a9ddf2 + 0717448 commit 69d251a

File tree

5 files changed

+155
-53
lines changed

5 files changed

+155
-53
lines changed

include/swift/ClangImporter/ClangImporter.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,9 @@ ImportDecl *createImportDecl(ASTContext &Ctx, DeclContext *DC, ClangNode ClangN,
492492
std::string
493493
getModuleCachePathFromClang(const clang::CompilerInstance &Instance);
494494

495+
/// Whether the given parameter name identifies a completion handler.
496+
bool isCompletionHandlerParamName(StringRef paramName);
497+
495498
} // end namespace swift
496499

497500
#endif

lib/ClangImporter/ImportName.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,8 +1189,7 @@ Optional<ForeignErrorConvention::Info> NameImporter::considerErrorImport(
11891189
return None;
11901190
}
11911191

1192-
/// Whether the given parameter name identifies a completion handler.
1193-
static bool isCompletionHandlerParamName(StringRef paramName) {
1192+
bool swift::isCompletionHandlerParamName(StringRef paramName) {
11941193
return paramName == "completionHandler" ||
11951194
paramName == "withCompletionHandler" ||
11961195
paramName == "completion" || paramName == "withCompletion" ||

lib/IDE/Refactoring.cpp

Lines changed: 85 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "swift/Frontend/Frontend.h"
2929
#include "swift/IDE/IDERequests.h"
3030
#include "swift/Index/Index.h"
31+
#include "swift/ClangImporter/ClangImporter.h"
3132
#include "swift/Parse/Lexer.h"
3233
#include "swift/Sema/IDETypeChecking.h"
3334
#include "swift/Subsystems.h"
@@ -3894,12 +3895,6 @@ namespace asyncrefactorings {
38943895

38953896
// TODO: Should probably split the refactorings into separate files
38963897

3897-
/// Whether the given parameter name identifies a completion callback
3898-
bool isCompletionHandlerName(StringRef Name) {
3899-
return Name.startswith("completion") || Name.contains("Completion") ||
3900-
Name.contains("Complete");
3901-
}
3902-
39033898
/// Whether the given type is the stdlib Result type
39043899
bool isResultType(Type Ty) {
39053900
if (!Ty)
@@ -4083,7 +4078,7 @@ struct AsyncHandlerDesc {
40834078
HandlerType Type = HandlerType::INVALID;
40844079
bool HasError = false;
40854080

4086-
static AsyncHandlerDesc find(const FuncDecl *FD, bool ignoreName = false) {
4081+
static AsyncHandlerDesc find(const FuncDecl *FD, bool ignoreName) {
40874082
if (!FD || FD->hasAsync() || FD->hasThrows())
40884083
return AsyncHandlerDesc();
40894084

@@ -4104,7 +4099,7 @@ struct AsyncHandlerDesc {
41044099

41054100
// Callback must have a completion-like name (if we're not ignoring it)
41064101
if (!ignoreName &&
4107-
!isCompletionHandlerName(HandlerDesc.Handler->getNameStr()))
4102+
!isCompletionHandlerParamName(HandlerDesc.Handler->getNameStr()))
41084103
return AsyncHandlerDesc();
41094104

41104105
// Callback must be a function type and return void. Doesn't need to have
@@ -4670,12 +4665,12 @@ struct CallbackClassifier {
46704665
}
46714666
};
46724667

4673-
/// Builds up async-converted code for any added AST nodes.
4668+
/// Builds up async-converted code for an AST node.
46744669
///
4675-
/// Function declarations will have `async` added. If a completion handler is
4676-
/// present, it will be removed and the return type of the function will
4677-
/// reflect the parameters of the handler, including an added `throws` if
4678-
/// necessary.
4670+
/// If it is a function, its declaration will have `async` added. If a
4671+
/// completion handler is present, it will be removed and the return type of
4672+
/// the function will reflect the parameters of the handler, including an
4673+
/// added `throws` if necessary.
46794674
///
46804675
/// Calls to the completion handler are replaced with either a `return` or
46814676
/// `throws` depending on the arguments.
@@ -4699,32 +4694,67 @@ struct CallbackClassifier {
46994694
/// The fallback is generally avoided, however, since it's quite unlikely to be
47004695
/// the code the user intended. In most cases the refactoring will continue,
47014696
/// with any unhandled decls wrapped in placeholders instead.
4702-
class AsyncConversionStringBuilder : private SourceEntityWalker {
4697+
class AsyncConverter : private SourceEntityWalker {
47034698
SourceManager &SM;
47044699
DiagnosticEngine &DiagEngine;
4700+
4701+
// Node to convert
4702+
ASTNode StartNode;
4703+
4704+
// Completion handler of `StartNode` (if it's a function with an async
4705+
// alternative)
47054706
const AsyncHandlerDesc &TopHandler;
47064707
SmallString<0> Buffer;
47074708
llvm::raw_svector_ostream OS;
47084709

4710+
// Decls where any force-unwrap of that decl should be unwrapped, eg. for a
4711+
// previously optional closure paramter has become a non-optional local
47094712
llvm::DenseSet<const Decl *> Unwraps;
4713+
// Decls whose references should be replaced with, either because they no
4714+
// longer exist or are a different type. Any replaced code should ideally be
4715+
// handled by the refactoring properly, but that's not possible in all cases
47104716
llvm::DenseSet<const Decl *> Placeholders;
4717+
// Mapping from decl -> name, used as both the name of possibly new local
4718+
// declarations of old parameters, as well as the replacement for any
4719+
// references to it
47114720
llvm::DenseMap<const Decl *, std::string> Names;
47124721

4722+
// These are per-node (ie. are saved and restored on each convertNode call)
47134723
SourceLoc LastAddedLoc;
47144724
int NestedExprCount = 0;
47154725

47164726
public:
4717-
AsyncConversionStringBuilder(SourceManager &SM, DiagnosticEngine &DiagEngine,
4718-
const AsyncHandlerDesc &TopHandler)
4719-
: SM(SM), DiagEngine(DiagEngine), TopHandler(TopHandler), Buffer(),
4720-
OS(Buffer) {
4727+
AsyncConverter(SourceManager &SM, DiagnosticEngine &DiagEngine,
4728+
ASTNode StartNode, const AsyncHandlerDesc &TopHandler)
4729+
: SM(SM), DiagEngine(DiagEngine), StartNode(StartNode),
4730+
TopHandler(TopHandler), Buffer(), OS(Buffer) {
47214731
Placeholders.insert(TopHandler.Handler);
47224732
}
47234733

4724-
void replace(ASTNode Node, SourceEditConsumer &EditConsumer) {
4725-
CharSourceRange Range =
4726-
Lexer::getCharSourceRangeFromSourceRange(SM, Node.getSourceRange());
4727-
EditConsumer.accept(SM, Range, Buffer.str());
4734+
bool convert() {
4735+
if (!Buffer.empty())
4736+
return !DiagEngine.hadAnyError();
4737+
4738+
if (auto *FD = dyn_cast_or_null<FuncDecl>(StartNode.dyn_cast<Decl *>())) {
4739+
addFuncDecl(FD);
4740+
if (FD->getBody()) {
4741+
convertNode(FD->getBody());
4742+
}
4743+
} else {
4744+
convertNode(StartNode);
4745+
}
4746+
return !DiagEngine.hadAnyError();
4747+
}
4748+
4749+
void replace(ASTNode Node, SourceEditConsumer &EditConsumer,
4750+
SourceLoc StartOverride = SourceLoc()) {
4751+
SourceRange Range = Node.getSourceRange();
4752+
if (StartOverride.isValid()) {
4753+
Range = SourceRange(StartOverride, Range.End);
4754+
}
4755+
CharSourceRange CharRange =
4756+
Lexer::getCharSourceRangeFromSourceRange(SM, Range);
4757+
EditConsumer.accept(SM, CharRange, Buffer.str());
47284758
Buffer.clear();
47294759
}
47304760

@@ -4734,13 +4764,7 @@ class AsyncConversionStringBuilder : private SourceEntityWalker {
47344764
Buffer.clear();
47354765
}
47364766

4737-
void convertFunction(const FuncDecl *FD) {
4738-
addFuncDecl(FD);
4739-
if (FD->getBody()) {
4740-
convertNode(FD->getBody());
4741-
}
4742-
}
4743-
4767+
private:
47444768
void convertNodes(ArrayRef<ASTNode> Nodes) {
47454769
for (auto Node : Nodes) {
47464770
OS << "\n";
@@ -4760,9 +4784,17 @@ class AsyncConversionStringBuilder : private SourceEntityWalker {
47604784
addRange(LastAddedLoc, Node.getEndLoc(), /*ToEndOfToken=*/true);
47614785
}
47624786

4763-
private:
47644787
bool walkToDeclPre(Decl *D, CharSourceRange Range) override {
4765-
return isa<PatternBindingDecl>(D);
4788+
if (isa<PatternBindingDecl>(D)) {
4789+
NestedExprCount++;
4790+
return true;
4791+
}
4792+
return false;
4793+
}
4794+
4795+
bool walkToDeclPost(Decl *D) override {
4796+
NestedExprCount--;
4797+
return true;
47664798
}
47674799

47684800
#define PLACEHOLDER_START "<#"
@@ -4800,9 +4832,8 @@ class AsyncConversionStringBuilder : private SourceEntityWalker {
48004832
[&]() { addHandlerCall(CE); });
48014833

48024834
if (auto *CE = dyn_cast<CallExpr>(E)) {
4803-
auto HandlerDesc =
4804-
AsyncHandlerDesc::find(getUnderlyingFunc(CE->getFn()),
4805-
/*ignoreName=*/true);
4835+
auto HandlerDesc = AsyncHandlerDesc::find(
4836+
getUnderlyingFunc(CE->getFn()), StartNode.dyn_cast<Expr *>() == CE);
48064837
if (HandlerDesc.isValid())
48074838
return addCustom(CE->getStartLoc(), CE->getEndLoc().getAdvancedLoc(1),
48084839
[&]() { addAsyncAlternativeCall(CE, HandlerDesc); });
@@ -4986,7 +5017,12 @@ class AsyncConversionStringBuilder : private SourceEntityWalker {
49865017
DiagEngine.diagnose(CE->getStartLoc(), diag::missing_callback_arg);
49875018
return;
49885019
}
5020+
49895021
auto Callback = dyn_cast<ClosureExpr>(ArgList.ref()[HandlerDesc.Index]);
5022+
auto Capture = dyn_cast<CaptureListExpr>(ArgList.ref()[HandlerDesc.Index]);
5023+
if (Capture) {
5024+
Callback = Capture->getClosureBody();
5025+
}
49905026
if (!Callback) {
49915027
DiagEngine.diagnose(CE->getStartLoc(), diag::missing_callback_arg);
49925028
return;
@@ -5119,12 +5155,17 @@ class AsyncConversionStringBuilder : private SourceEntityWalker {
51195155
/*ToEndOfToken=*/true);
51205156

51215157
OS << tok::l_paren;
5158+
size_t realArgCount = 0;
51225159
for (size_t I = 0, E = Args.size() - 1; I < E; ++I) {
5123-
if (I > 0)
5160+
if (isa<DefaultArgumentExpr>(Args[I]))
5161+
continue;
5162+
5163+
if (realArgCount > 0)
51245164
OS << tok::comma << " ";
51255165
// Can't just add the range as we need to perform replacements
51265166
convertNode(Args[I], /*StartOverride=*/CE->getArgumentLabelLoc(I),
51275167
/*ConvertCalls=*/false);
5168+
realArgCount++;
51285169
}
51295170
OS << tok::r_paren;
51305171
}
@@ -5249,13 +5290,11 @@ bool RefactoringActionConvertCallToAsyncAlternative::performChange() {
52495290
"Should not run performChange when refactoring is not applicable");
52505291

52515292
AsyncHandlerDesc TempDesc;
5252-
AsyncConversionStringBuilder Builder(SM, DiagEngine, TempDesc);
5253-
Builder.convertNode(CE);
5254-
5255-
if (DiagEngine.hadAnyError())
5293+
AsyncConverter Converter(SM, DiagEngine, CE, TempDesc);
5294+
if (!Converter.convert())
52565295
return true;
52575296

5258-
Builder.replace(CE, EditConsumer);
5297+
Converter.replace(CE, EditConsumer);
52595298
return false;
52605299
}
52615300

@@ -5279,13 +5318,11 @@ bool RefactoringActionConvertToAsync::performChange() {
52795318
"Should not run performChange when refactoring is not applicable");
52805319

52815320
AsyncHandlerDesc TempDesc;
5282-
AsyncConversionStringBuilder Builder(SM, DiagEngine, TempDesc);
5283-
Builder.convertFunction(FD);
5284-
5285-
if (DiagEngine.hadAnyError())
5321+
AsyncConverter Converter(SM, DiagEngine, FD, TempDesc);
5322+
if (!Converter.convert())
52865323
return true;
52875324

5288-
Builder.replace(FD, EditConsumer);
5325+
Converter.replace(FD, EditConsumer, FD->getSourceRangeIncludingAttrs().Start);
52895326
return false;
52905327
}
52915328

@@ -5318,16 +5355,14 @@ bool RefactoringActionAddAsyncAlternative::performChange() {
53185355
assert(HandlerDesc.isValid() &&
53195356
"Should not run performChange when refactoring is not applicable");
53205357

5321-
AsyncConversionStringBuilder Builder(SM, DiagEngine, HandlerDesc);
5322-
Builder.convertFunction(FD);
5323-
5324-
if (DiagEngine.hadAnyError())
5358+
AsyncConverter Converter(SM, DiagEngine, FD, HandlerDesc);
5359+
if (!Converter.convert())
53255360
return true;
53265361

53275362
EditConsumer.accept(SM, FD->getAttributeInsertionLoc(false),
53285363
"@available(*, deprecated, message: \"Prefer async "
53295364
"alternative instead\")\n");
5330-
Builder.insertAfter(FD, EditConsumer);
5365+
Converter.insertAfter(FD, EditConsumer);
53315366

53325367
return false;
53335368
}

test/refactoring/ConvertAsync/basic.swift

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ func genericResult<T>(completion: (T?, Error?) -> Void) where T: Numeric { }
108108
func genericError<E>(completion: (String?, E?) -> Void) where E: Error { }
109109
// GENERIC-ERROR: func genericError<E>() async throws -> String where E: Error { }
110110

111+
// RUN: %refactor -add-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=OTHER-NAME %s
112+
func otherName(execute: (String) -> Void) { }
113+
// OTHER-NAME: func otherName() async -> String { }
114+
115+
// RUN: %refactor -add-async-alternative -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=DEFAULT_ARGS %s
116+
func defaultArgs(a: Int, b: Int = 10, completion: (String) -> Void) { }
117+
// DEFAULT_ARGS: func defaultArgs(a: Int, b: Int = 10) async -> String { }
118+
111119
struct MyStruct {
112120
var someVar: (Int) -> Void {
113121
get {
@@ -171,7 +179,7 @@ func noParamAutoclosure(completion: @autoclosure () -> Void) { }
171179
// 2. Check that the various ways to call a function (and the positions the
172180
// refactoring is called from) are handled correctly
173181

174-
// RUN: %refactor -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefixes=CONVERT-FUNC,CALL,CALL-NOLABEL,CALL-WRAPPED,TRAILING,TRAILING-PARENS,TRAILING-WRAPPED,TRAILING-ARG %s
182+
// RUN: %refactor -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefixes=CONVERT-FUNC,CALL,CALL-NOLABEL,CALL-WRAPPED,TRAILING,TRAILING-PARENS,TRAILING-WRAPPED,CALL-ARG,MANY-CALL,MEMBER-CALL,MEMBER-CALL2,MEMBER-PARENS,EMPTY-CAPTURE,CAPTURE,DEFAULT-ARGS-MISSING,DEFAULT-ARGS-CALL %s
175183
func testCalls() {
176184
// CONVERT-FUNC: {{^}}func testCalls() async {
177185
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+4):3 | %FileCheck -check-prefix=CALL %s
@@ -266,8 +274,51 @@ func testCalls() {
266274
let _: Void = simple { str in
267275
print("assigned")
268276
}
277+
// CONVERT-FUNC: let _: Void = simple { str in{{$}}
278+
// CONVERT-FUNC-NEXT: print("assigned"){{$}}
279+
// CONVERT-FUNC-NEXT: }{{$}}
269280

270281
// RUN: not %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3
271282
noParamAutoclosure(completion: print("autoclosure"))
283+
// CONVERT-FUNC: noParamAutoclosure(completion: print("autoclosure")){{$}}
284+
285+
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=EMPTY-CAPTURE %s
286+
simple { [] str in
287+
print("closure with empty capture list")
288+
}
289+
// EMPTY-CAPTURE: let str = await simple(){{$}}
290+
// EMPTY-CAPTURE-NEXT: {{^}}print("closure with empty capture list")
291+
292+
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+2):3 | %FileCheck -check-prefix=CAPTURE %s
293+
let anything = "anything"
294+
simple { [unowned anything] str in
295+
print("closure with capture list \(anything)")
296+
}
297+
// CAPTURE: let str = await simple(){{$}}
298+
// CAPTURE-NEXT: {{^}}print("closure with capture list \(anything)")
299+
300+
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=OTHER-DIRECT %s
301+
otherName(execute: { str in
302+
print("otherName")
303+
})
304+
// OTHER-DIRECT: let str = await otherName(){{$}}
305+
// OTHER-DIRECT-NEXT: {{^}}print("otherName")
306+
// CONVERT-FUNC: otherName(execute: { str in{{$}}
307+
// CONVERT-FUNC-NEXT: print("otherName"){{$}}
308+
// CONVERT-FUNC-NEXT: }){{$}}
309+
310+
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=DEFAULT-ARGS-MISSING %s
311+
defaultArgs(a: 1) { str in
312+
print("defaultArgs missing")
313+
}
314+
// DEFAULT-ARGS-MISSING: let str = await defaultArgs(a: 1){{$}}
315+
// DEFAULT-ARGS-MISSING-NEXT: {{^}}print("defaultArgs missing")
316+
317+
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=DEFAULT-ARGS-CALL %s
318+
defaultArgs(a: 1, b: 2) { str in
319+
print("defaultArgs")
320+
}
321+
// DEFAULT-ARGS-CALL: let str = await defaultArgs(a: 1, b: 2){{$}}
322+
// DEFAULT-ARGS-CALL-NEXT: {{^}}print("defaultArgs")
272323
}
273324
// CONVERT-FUNC: {{^}}}

test/refactoring/ConvertAsync/convert_function.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ func nested() {
2222
// NESTED-NEXT: print(str2)
2323
// NESTED-NEXT: }
2424

25+
// RUN: %refactor -convert-to-async -dump-text -source-filename %s -pos=%(line+2):9 | %FileCheck -check-prefix=ATTRIBUTES %s
26+
@available(*, deprecated, message: "Deprecated")
27+
private func functionWithAttributes() {
28+
simple { str in
29+
print(str)
30+
}
31+
}
32+
// ATTRIBUTES: convert_function.swift [[# @LINE-6]]:1 -> [[# @LINE-1]]:2
33+
// ATTRIBUTES-NEXT: @available(*, deprecated, message: "Deprecated")
34+
// ATTRIBUTES-NEXT: private func functionWithAttributes() async {
35+
// ATTRIBUTES-NEXT: let str = await simple()
36+
// ATTRIBUTES-NEXT: print(str)
37+
// ATTRIBUTES-NEXT: }
38+
2539
// RUN: %refactor -convert-to-async -dump-text -source-filename %s -pos=%(line+1):1 | %FileCheck -check-prefix=MANY-NESTED %s
2640
func manyNested() {
2741
simple { str1 in

0 commit comments

Comments
 (0)