Skip to content

Commit 384b309

Browse files
committed
[Refactoring] Only convert calls with handlers that have completion-like names
Still convert the call if it was requested directly - only check the name when converting a whole function. Once we have an attribute, we should use that instead.
1 parent bc52297 commit 384b309

File tree

4 files changed

+87
-47
lines changed

4 files changed

+87
-47
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: 65 additions & 45 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,28 +4694,58 @@ 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

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+
47244749
void replace(ASTNode Node, SourceEditConsumer &EditConsumer,
47254750
SourceLoc StartOverride = SourceLoc()) {
47264751
SourceRange Range = Node.getSourceRange();
@@ -4739,13 +4764,7 @@ class AsyncConversionStringBuilder : private SourceEntityWalker {
47394764
Buffer.clear();
47404765
}
47414766

4742-
void convertFunction(const FuncDecl *FD) {
4743-
addFuncDecl(FD);
4744-
if (FD->getBody()) {
4745-
convertNode(FD->getBody());
4746-
}
4747-
}
4748-
4767+
private:
47494768
void convertNodes(ArrayRef<ASTNode> Nodes) {
47504769
for (auto Node : Nodes) {
47514770
OS << "\n";
@@ -4765,9 +4784,17 @@ class AsyncConversionStringBuilder : private SourceEntityWalker {
47654784
addRange(LastAddedLoc, Node.getEndLoc(), /*ToEndOfToken=*/true);
47664785
}
47674786

4768-
private:
47694787
bool walkToDeclPre(Decl *D, CharSourceRange Range) override {
4770-
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;
47714798
}
47724799

47734800
#define PLACEHOLDER_START "<#"
@@ -4805,9 +4832,8 @@ class AsyncConversionStringBuilder : private SourceEntityWalker {
48054832
[&]() { addHandlerCall(CE); });
48064833

48074834
if (auto *CE = dyn_cast<CallExpr>(E)) {
4808-
auto HandlerDesc =
4809-
AsyncHandlerDesc::find(getUnderlyingFunc(CE->getFn()),
4810-
/*ignoreName=*/true);
4835+
auto HandlerDesc = AsyncHandlerDesc::find(
4836+
getUnderlyingFunc(CE->getFn()), StartNode.dyn_cast<Expr *>() == CE);
48114837
if (HandlerDesc.isValid())
48124838
return addCustom(CE->getStartLoc(), CE->getEndLoc().getAdvancedLoc(1),
48134839
[&]() { addAsyncAlternativeCall(CE, HandlerDesc); });
@@ -5259,13 +5285,11 @@ bool RefactoringActionConvertCallToAsyncAlternative::performChange() {
52595285
"Should not run performChange when refactoring is not applicable");
52605286

52615287
AsyncHandlerDesc TempDesc;
5262-
AsyncConversionStringBuilder Builder(SM, DiagEngine, TempDesc);
5263-
Builder.convertNode(CE);
5264-
5265-
if (DiagEngine.hadAnyError())
5288+
AsyncConverter Converter(SM, DiagEngine, CE, TempDesc);
5289+
if (!Converter.convert())
52665290
return true;
52675291

5268-
Builder.replace(CE, EditConsumer);
5292+
Converter.replace(CE, EditConsumer);
52695293
return false;
52705294
}
52715295

@@ -5289,13 +5313,11 @@ bool RefactoringActionConvertToAsync::performChange() {
52895313
"Should not run performChange when refactoring is not applicable");
52905314

52915315
AsyncHandlerDesc TempDesc;
5292-
AsyncConversionStringBuilder Builder(SM, DiagEngine, TempDesc);
5293-
Builder.convertFunction(FD);
5294-
5295-
if (DiagEngine.hadAnyError())
5316+
AsyncConverter Converter(SM, DiagEngine, FD, TempDesc);
5317+
if (!Converter.convert())
52965318
return true;
52975319

5298-
Builder.replace(FD, EditConsumer, FD->getSourceRangeIncludingAttrs().Start);
5320+
Converter.replace(FD, EditConsumer, FD->getSourceRangeIncludingAttrs().Start);
52995321
return false;
53005322
}
53015323

@@ -5328,16 +5350,14 @@ bool RefactoringActionAddAsyncAlternative::performChange() {
53285350
assert(HandlerDesc.isValid() &&
53295351
"Should not run performChange when refactoring is not applicable");
53305352

5331-
AsyncConversionStringBuilder Builder(SM, DiagEngine, HandlerDesc);
5332-
Builder.convertFunction(FD);
5333-
5334-
if (DiagEngine.hadAnyError())
5353+
AsyncConverter Converter(SM, DiagEngine, FD, HandlerDesc);
5354+
if (!Converter.convert())
53355355
return true;
53365356

53375357
EditConsumer.accept(SM, FD->getAttributeInsertionLoc(false),
53385358
"@available(*, deprecated, message: \"Prefer async "
53395359
"alternative instead\")\n");
5340-
Builder.insertAfter(FD, EditConsumer);
5360+
Converter.insertAfter(FD, EditConsumer);
53415361

53425362
return false;
53435363
}

test/refactoring/ConvertAsync/basic.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ 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+
111115
struct MyStruct {
112116
var someVar: (Int) -> Void {
113117
get {
@@ -266,9 +270,13 @@ func testCalls() {
266270
let _: Void = simple { str in
267271
print("assigned")
268272
}
273+
// CONVERT-FUNC: let _: Void = simple { str in{{$}}
274+
// CONVERT-FUNC-NEXT: print("assigned"){{$}}
275+
// CONVERT-FUNC-NEXT: }{{$}}
269276

270277
// RUN: not %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3
271278
noParamAutoclosure(completion: print("autoclosure"))
279+
// CONVERT-FUNC: noParamAutoclosure(completion: print("autoclosure")){{$}}
272280

273281
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=EMPTY-CAPTURE %s
274282
simple { [] str in
@@ -284,5 +292,15 @@ func testCalls() {
284292
}
285293
// CAPTURE: let str = await simple(){{$}}
286294
// CAPTURE-NEXT: {{^}}print("closure with capture list \(anything)")
295+
296+
// RUN: %refactor -convert-call-to-async-alternative -dump-text -source-filename %s -pos=%(line+1):3 | %FileCheck -check-prefix=OTHER-DIRECT %s
297+
otherName(execute: { str in
298+
print("otherName")
299+
})
300+
// OTHER-DIRECT: let str = await otherName(){{$}}
301+
// OTHER-DIRECT-NEXT: {{^}}print("otherName")
302+
// CONVERT-FUNC: otherName(execute: { str in{{$}}
303+
// CONVERT-FUNC-NEXT: print("otherName"){{$}}
304+
// CONVERT-FUNC-NEXT: }){{$}}
287305
}
288306
// CONVERT-FUNC: {{^}}}

0 commit comments

Comments
 (0)