Skip to content

Commit 28572b3

Browse files
committed
Emit fix-it for diagnostic
This patch teaches the compiler to emit a fix-it to automatically wrap everything in the function in a Task and remove the effects from the function declaration.
1 parent c6b6bcd commit 28572b3

File tree

3 files changed

+84
-6
lines changed

3 files changed

+84
-6
lines changed

lib/Sema/TypeCheckAttr.cpp

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,68 @@ void AttributeChecker::visitDynamicAttr(DynamicAttr *attr) {
475475
diagnoseAndRemoveAttr(attr, diag::dynamic_with_transparent);
476476
}
477477

478+
/// Replaces asynchronous IBActionAttr/IBSegueActionAttr function declarations
479+
/// with a synchronous function. The body of the original function is moved
480+
/// inside of a task executed on the MainActor
481+
static void emitFixItIBActionRemoveAsync(ASTContext &ctx, const FuncDecl &FD) {
482+
// If we don't have an async loc for some reason, things will explode
483+
if (!FD.getAsyncLoc())
484+
return;
485+
486+
std::string replacement = "";
487+
488+
// attributes, function name and everything up to `async` (exclusive)
489+
replacement += CharSourceRange(ctx.SourceMgr,
490+
FD.getSourceRangeIncludingAttrs().Start,
491+
FD.getAsyncLoc()).str();
492+
493+
CharSourceRange returnType = Lexer::getCharSourceRangeFromSourceRange(ctx.SourceMgr,
494+
FD.getResultTypeSourceRange());
495+
496+
497+
// If we have a return type, include that here
498+
if (returnType.isValid()) {
499+
replacement += (llvm::Twine("-> ") + Lexer::getCharSourceRangeFromSourceRange(ctx.SourceMgr,
500+
FD.getResultTypeSourceRange()).str()).str();
501+
}
502+
503+
504+
if (!FD.hasBody()) {
505+
// If we don't have any body, the sourcelocs won't work and will result in
506+
// crashes, so just swap out what we can
507+
508+
SourceLoc endLoc = returnType.isValid() ? returnType.getEnd() : FD.getAsyncLoc();
509+
ctx.Diags.diagnose(FD.getAsyncLoc(), diag::remove_async_add_task,
510+
FD.getName())
511+
.fixItReplace(SourceRange(FD.getSourceRangeIncludingAttrs().Start, endLoc),
512+
replacement);
513+
return;
514+
}
515+
516+
if (returnType.isValid())
517+
replacement += " "; // insert space between type name and lbrace
518+
519+
replacement += "{\nTask { @MainActor in";
520+
521+
// If the body of the function is just "{}", there isn't anything to wrap.
522+
// stepping over the braces to grab just the body will result in the `Start`
523+
// location of the source range to come after the `End` of the range, and we
524+
// will overflow. Dance around this by just appending the end of the fix to
525+
// the replacement.
526+
if (FD.getBody()->getLBraceLoc() != FD.getBody()->getRBraceLoc().getAdvancedLocOrInvalid(-1)) {
527+
// We actually have a body, so add that to the string
528+
CharSourceRange functionBody(ctx.SourceMgr,
529+
FD.getBody()->getLBraceLoc().getAdvancedLocOrInvalid(1),
530+
FD.getBody()->getRBraceLoc().getAdvancedLocOrInvalid(-1));
531+
replacement += functionBody.str();
532+
}
533+
replacement += " }\n}";
534+
535+
ctx.Diags.diagnose(FD.getAsyncLoc(), diag::remove_async_add_task, FD.getName())
536+
.fixItReplace(SourceRange(FD.getSourceRangeIncludingAttrs().Start,
537+
FD.getBody()->getRBraceLoc()), replacement);
538+
}
539+
478540
static bool
479541
validateIBActionSignature(ASTContext &ctx, DeclAttribute *attr,
480542
const FuncDecl *FD, unsigned minParameters,
@@ -504,9 +566,7 @@ validateIBActionSignature(ASTContext &ctx, DeclAttribute *attr,
504566
if (FD->isAsyncContext()) {
505567
ctx.Diags.diagnose(FD->getAsyncLoc(), diag::attr_decl_async,
506568
attr->getAttrName(), FD->getDescriptiveKind());
507-
508-
ctx.Diags.diagnose(FD->getAsyncLoc(), diag::remove_async_add_task,
509-
FD->getName());
569+
emitFixItIBActionRemoveAsync(ctx, *FD);
510570
valid = false;
511571
}
512572

test/attr/attr_ibaction.swift

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,24 @@ class IBActionWrapperTy {
4242
@objc @IBAction
4343
func evenMoreMagic(_: AnyObject) -> () {} // no-warning
4444

45-
@available(macOS 10.15, *)
45+
@available(SwiftStdlib 5.5, *)
4646
@IBAction
47-
func asyncIBAction(_: AnyObject) async -> () {}
47+
func asyncIBActionNoSpace(_: AnyObject) async -> () {}
4848
// expected-error@-1 {{@IBAction instance method cannot be async}}
49-
// expected-note@-2 {{remove 'async' and wrap in 'Task' to use concurrency in 'asyncIBAction'}}
49+
// expected-note@-2 {{remove 'async' and wrap in 'Task' to use concurrency in 'asyncIBActionNoSpace'}}{{45:3-47:57=@available(SwiftStdlib 5.5, *)\n @IBAction\n func asyncIBActionNoSpace(_: AnyObject) -> () {\nTask { @MainActor in \}\n\}}}
50+
51+
@available(SwiftStdlib 5.5, *)
52+
@IBAction
53+
func asyncIBActionWithFullBody(_: AnyObject) async {
54+
print("Hello World")
55+
}
56+
// expected-error@-3 {{@IBAction instance method cannot be async}}
57+
// expected-note@-4 {{remove 'async' and wrap in 'Task' to use concurrency in 'asyncIBActionWithFullBody'}}{{51:3-55:4=@available(SwiftStdlib 5.5, *)\n @IBAction\n func asyncIBActionWithFullBody(_: AnyObject) {\nTask { @MainActor in\n print("Hello World")\n \}\n\}}}
58+
59+
@available(SwiftStdlib 5.5, *) @IBAction func asyncIBActionNoBody(_: AnyObject) async
60+
// expected-error@-1 {{expected '{' in body of function declaration}}
61+
// expected-error@-2 {{@IBAction instance method cannot be asynchronous}}
62+
// expected-note@-3 {{remove 'async' and wrap in 'Task' to use concurrency in 'asyncIBActionNoBody}}{{3-88=@available(SwiftStdlib 5.5, *) @IBAction func asyncIBActionNoBody(_: AnyObject)}}
5063

5164
}
5265

test/attr/attr_ibsegueaction.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ class IBActionWrapperTy {
4545
func moreMagic(_: AnyObject, _: AnyObject, _: AnyObject) -> AnyObject {} // no-warning
4646
@objc @IBSegueAction
4747
func evenMoreMagic(_: AnyObject, _: AnyObject, _: AnyObject) -> AnyObject {} // no-warning
48+
49+
@available(SwiftStdlib 5.5, *) @IBSegueAction
50+
func process(_: AnyObject, _: AnyObject, _: AnyObject) async -> AnyObject { }
51+
// expected-error@-1 {{@IBSegueAction instance method cannot be asynchronous}}
52+
// expected-note@-2 {{remove 'async' and wrap in 'Task' to use concurrency in 'process'}}{{49:3-50:80=@available(SwiftStdlib 5.5, *) @IBSegueAction\n func process(_: AnyObject, _: AnyObject, _: AnyObject) -> AnyObject {\nTask { @MainActor in \}\n\}}}
4853
}
4954

5055
struct S { }

0 commit comments

Comments
 (0)