Skip to content

Commit f755904

Browse files
authored
Merge pull request #59205 from etcwilde/ewilde/no-async-ibactions
Disallow async IBAction methods
2 parents a8ecfc9 + 129b9c0 commit f755904

File tree

6 files changed

+110
-5
lines changed

6 files changed

+110
-5
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,6 +1451,8 @@ NOTE(fixit_rename_in_swift,none,
14511451
"change Swift name to %0", (DeclName))
14521452
NOTE(fixit_rename_in_objc,none,
14531453
"change Objective-C selector to %0", (ObjCSelector))
1454+
NOTE(remove_async_add_task,none,
1455+
"remove 'async' and wrap in 'Task' to use concurrency in %0", (DeclName))
14541456
ERROR(no_objc_tagged_pointer_not_class_protocol,none,
14551457
"@unsafe_no_objc_tagged_pointer can only be applied to class protocols",
14561458
())
@@ -1464,11 +1466,12 @@ ERROR(cdecl_empty_name,none,
14641466
"@_cdecl symbol name cannot be empty", ())
14651467
ERROR(cdecl_throws,none,
14661468
"raising errors from @_cdecl functions is not supported", ())
1467-
ERROR(cdecl_async,none,
1468-
"@_cdecl functions cannot be asynchronous", ())
14691469

14701470
ERROR(attr_methods_only,none,
14711471
"only methods can be declared %0", (DeclAttribute))
1472+
ERROR(attr_decl_async,none,
1473+
"@%0 %1 cannot be asynchronous", (StringRef, DescriptiveDeclKind))
1474+
14721475
ERROR(access_control_in_protocol,none,
14731476
"%0 modifier cannot be used in protocols", (DeclAttribute))
14741477
NOTE(access_control_in_protocol_detail,none,

lib/Sema/TypeCheckAttr.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,75 @@ void AttributeChecker::visitDynamicAttr(DynamicAttr *attr) {
476476
diagnoseAndRemoveAttr(attr, diag::dynamic_with_transparent);
477477
}
478478

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

574+
if (FD->isAsyncContext()) {
575+
ctx.Diags.diagnose(FD->getAsyncLoc(), diag::attr_decl_async,
576+
attr->getAttrName(), FD->getDescriptiveKind());
577+
emitFixItIBActionRemoveAsync(ctx, *FD);
578+
valid = false;
579+
}
580+
505581
// We don't need to check here that parameter or return types are
506582
// ObjC-representable; IsObjCRequest will validate that.
507583

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2972,8 +2972,9 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
29722972
if (isRepresentableInObjC(FD, reason, asyncConvention, errorConvention)) {
29732973
if (FD->hasAsync()) {
29742974
FD->setForeignAsyncConvention(*asyncConvention);
2975-
getASTContext().Diags.diagnose(CDeclAttr->getLocation(),
2976-
diag::cdecl_async);
2975+
getASTContext().Diags.diagnose(
2976+
CDeclAttr->getLocation(), diag::attr_decl_async,
2977+
CDeclAttr->getAttrName(), FD->getDescriptiveKind());
29772978
} else if (FD->hasThrows()) {
29782979
FD->setForeignErrorConvention(*errorConvention);
29792980
getASTContext().Diags.diagnose(CDeclAttr->getLocation(),

test/attr/attr_cdecl_async.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
// REQUIRES: concurrency
44

5-
@_cdecl("async") // expected-error{{@_cdecl functions cannot be asynchronous}}
5+
@_cdecl("async") // expected-error{{@_cdecl global function cannot be asynchronous}}
66
func asynchronous() async { }
77

test/attr/attr_ibaction.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,26 @@ class IBActionWrapperTy {
4141
func moreMagic(_: AnyObject) -> () {} // no-warning
4242
@objc @IBAction
4343
func evenMoreMagic(_: AnyObject) -> () {} // no-warning
44+
45+
@available(SwiftStdlib 5.5, *)
46+
@IBAction
47+
func asyncIBActionNoSpace(_: AnyObject) async -> () {}
48+
// expected-error@-1 {{@IBAction instance method cannot be async}}
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)}}
63+
4464
}
4565

4666
struct S { }

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)