Skip to content

Commit 393682d

Browse files
authored
Merge pull request swiftlang#33476 from DougGregor/concurrency-asynchandler
[Concurrency] Add @asyncHandler attribute.
2 parents 8fa18a4 + 8cd892a commit 393682d

File tree

11 files changed

+182
-10
lines changed

11 files changed

+182
-10
lines changed

include/swift/AST/Attr.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,11 @@ SIMPLE_DECL_ATTR(noDerivative, NoDerivative,
560560
ABIBreakingToAdd | ABIBreakingToRemove | APIBreakingToAdd | APIBreakingToRemove,
561561
100)
562562

563+
SIMPLE_DECL_ATTR(asyncHandler, AsyncHandler,
564+
OnFunc | UserInaccessible |
565+
ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
566+
101)
567+
563568
#undef TYPE_ATTR
564569
#undef DECL_ATTR_ALIAS
565570
#undef CONTEXTUAL_DECL_ATTR_ALIAS

include/swift/AST/Decl.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5888,6 +5888,13 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
58885888
/// type of the function will be `async` as well.
58895889
bool hasAsync() const { return Bits.AbstractFunctionDecl.Async; }
58905890

5891+
/// Returns true if the function is a suitable 'async' context.
5892+
///
5893+
/// Functions that are an 'async' context can make calls to 'async' functions.
5894+
bool isAsyncContext() const {
5895+
return hasAsync() || getAttrs().hasAttribute<AsyncHandlerAttr>();
5896+
}
5897+
58915898
/// Returns true if the function body throws.
58925899
bool hasThrows() const { return Bits.AbstractFunctionDecl.Throws; }
58935900

include/swift/AST/DiagnosticsSema.def

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4047,7 +4047,7 @@ WARNING(no_throw_in_do_with_catch,none,
40474047
ERROR(async_call_without_await,none,
40484048
"call is 'async' but is not marked with 'await'", ())
40494049
ERROR(async_call_without_await_in_autoclosure,none,
4050-
"call is 'async' in an autoclosure argument is not marked with 'await'", ())
4050+
"call is 'async' in an autoclosure argument that is not marked with 'await'", ())
40514051
WARNING(no_async_in_await,none,
40524052
"no calls to 'async' functions occur within 'await' expression", ())
40534053
ERROR(async_call_in_illegal_context,none,
@@ -4064,6 +4064,8 @@ ERROR(async_in_nonasync_function,none,
40644064
(bool, bool))
40654065
NOTE(note_add_async_to_function,none,
40664066
"add 'async' to function %0 to make it asynchronous", (DeclName))
4067+
NOTE(note_add_asynchandler_to_function,none,
4068+
"add '@asyncHandler' to function %0 to create an implicit asynchronous context", (DeclName))
40674069
ERROR(not_objc_function_async,none,
40684070
"'async' function cannot be represented in Objective-C", ())
40694071
NOTE(not_objc_function_type_async,none,
@@ -4074,6 +4076,28 @@ NOTE(protocol_witness_async_conflict,none,
40744076
ERROR(async_autoclosure_nonasync_function,none,
40754077
"'async' autoclosure parameter in a non-'async' function", ())
40764078

4079+
ERROR(asynchandler_attr_requires_concurrency,none,
4080+
"'@asyncHandler' is only valid when experimental concurrency is enabled",
4081+
())
4082+
ERROR(asynchandler_non_func,none,
4083+
"'@asyncHandler' can only be applied to functions",
4084+
())
4085+
ERROR(asynchandler_returns_value,none,
4086+
"'@asyncHandler' function can only return 'Void'",
4087+
())
4088+
ERROR(asynchandler_throws,none,
4089+
"'@asyncHandler' function cannot throw",
4090+
())
4091+
ERROR(asynchandler_async,none,
4092+
"'@asyncHandler' function cannot be 'async' itself",
4093+
())
4094+
ERROR(asynchandler_inout_parameter,none,
4095+
"'inout' parameter is not allowed in '@asyncHandler' function",
4096+
())
4097+
ERROR(asynchandler_mutating,none,
4098+
"'@asyncHandler' function cannot be 'mutating'",
4099+
())
4100+
40774101
//------------------------------------------------------------------------------
40784102
// MARK: Type Check Types
40794103
//------------------------------------------------------------------------------

lib/Sema/TypeCheckAttr.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,66 @@
4141

4242
using namespace swift;
4343

44+
/// Check whether the @asyncHandler attribute can be applied to the given
45+
/// function declaration.
46+
///
47+
/// \param diagnose Whether to emit a diagnostic when a problem is encountered.
48+
///
49+
/// \returns \c true if there was a problem with adding the attribute, \c false
50+
/// otherwise.
51+
static bool checkAsyncHandler(FuncDecl *func, bool diagnose) {
52+
if (!func->getResultInterfaceType()->isVoid()) {
53+
if (diagnose) {
54+
func->diagnose(diag::asynchandler_returns_value)
55+
.highlight(func->getBodyResultTypeLoc().getSourceRange());
56+
}
57+
58+
return true;
59+
}
60+
61+
if (func->hasThrows()) {
62+
if (diagnose) {
63+
func->diagnose(diag::asynchandler_throws)
64+
.fixItRemove(func->getThrowsLoc());
65+
}
66+
67+
return true;
68+
}
69+
70+
if (func->hasAsync()) {
71+
if (diagnose) {
72+
func->diagnose(diag::asynchandler_async)
73+
.fixItRemove(func->getAsyncLoc());
74+
}
75+
76+
return true;
77+
}
78+
79+
for (auto param : *func->getParameters()) {
80+
if (param->isInOut()) {
81+
if (diagnose) {
82+
param->diagnose(diag::asynchandler_inout_parameter)
83+
.fixItRemove(param->getSpecifierLoc());
84+
}
85+
86+
return true;
87+
}
88+
}
89+
90+
if (func->isMutating()) {
91+
if (diagnose) {
92+
auto diag = func->diagnose(diag::asynchandler_mutating);
93+
if (auto mutatingAttr = func->getAttrs().getAttribute<MutatingAttr>()) {
94+
diag.fixItRemove(mutatingAttr->getRange());
95+
}
96+
}
97+
98+
return true;
99+
}
100+
101+
return false;
102+
}
103+
44104
namespace {
45105
/// This emits a diagnostic with a fixit to remove the attribute.
46106
template<typename ...ArgTypes>
@@ -257,6 +317,24 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
257317
void visitDifferentiableAttr(DifferentiableAttr *attr);
258318
void visitDerivativeAttr(DerivativeAttr *attr);
259319
void visitTransposeAttr(TransposeAttr *attr);
320+
321+
void visitAsyncHandlerAttr(AsyncHandlerAttr *attr) {
322+
if (!Ctx.LangOpts.EnableExperimentalConcurrency) {
323+
diagnoseAndRemoveAttr(attr, diag::asynchandler_attr_requires_concurrency);
324+
return;
325+
}
326+
327+
auto func = dyn_cast<FuncDecl>(D);
328+
if (!func) {
329+
diagnoseAndRemoveAttr(attr, diag::asynchandler_non_func);
330+
return;
331+
}
332+
333+
if (checkAsyncHandler(func, /*diagnose=*/true)) {
334+
attr->setInvalid();
335+
return;
336+
}
337+
}
260338
};
261339
} // end anonymous namespace
262340

@@ -5148,3 +5226,13 @@ void AttributeChecker::visitTransposeAttr(TransposeAttr *attr) {
51485226
// Set the resolved linearity parameter indices in the attribute.
51495227
attr->setParameterIndices(linearParamIndices);
51505228
}
5229+
5230+
void swift::addAsyncNotes(FuncDecl *func) {
5231+
func->diagnose(diag::note_add_async_to_function, func->getName());
5232+
5233+
if (!checkAsyncHandler(func, /*diagnose=*/false)) {
5234+
func->diagnose(
5235+
diag::note_add_asynchandler_to_function, func->getName())
5236+
.fixItInsert(func->getAttributeInsertionLoc(false), "@asyncHandler ");
5237+
}
5238+
}

lib/Sema/TypeCheckDecl.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2155,11 +2155,10 @@ static Type validateParameterType(ParamDecl *decl) {
21552155
if (auto fnType = Ty->getAs<FunctionType>()) {
21562156
if (fnType->isAsync() &&
21572157
!(isa<AbstractFunctionDecl>(dc) &&
2158-
cast<AbstractFunctionDecl>(dc)->hasAsync())) {
2158+
cast<AbstractFunctionDecl>(dc)->isAsyncContext())) {
21592159
decl->diagnose(diag::async_autoclosure_nonasync_function);
2160-
if (auto func = dyn_cast<FuncDecl>(dc)) {
2161-
func->diagnose(diag::note_add_async_to_function, func->getName());
2162-
}
2160+
if (auto func = dyn_cast<FuncDecl>(dc))
2161+
addAsyncNotes(func);
21632162
}
21642163
}
21652164
}

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,6 +1416,7 @@ namespace {
14161416
UNINTERESTING_ATTR(AccessControl)
14171417
UNINTERESTING_ATTR(Alignment)
14181418
UNINTERESTING_ATTR(AlwaysEmitIntoClient)
1419+
UNINTERESTING_ATTR(AsyncHandler)
14191420
UNINTERESTING_ATTR(Borrowed)
14201421
UNINTERESTING_ATTR(CDecl)
14211422
UNINTERESTING_ATTR(Consuming)

lib/Sema/TypeCheckEffects.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,7 @@ class Context {
932932
}
933933
}
934934

935-
return Context(D->hasThrows(), D->hasAsync(), AnyFunctionRef(D));
935+
return Context(D->hasThrows(), D->isAsyncContext(), AnyFunctionRef(D));
936936
}
937937

938938
static Context forDeferBody() {
@@ -1254,7 +1254,7 @@ class Context {
12541254
if (!func)
12551255
return;
12561256

1257-
func->diagnose(diag::note_add_async_to_function, func->getName());
1257+
addAsyncNotes(func);
12581258
}
12591259

12601260
void diagnoseUnhandledAsyncSite(DiagnosticEngine &Diags, ASTNode node) {

lib/Sema/TypeChecker.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,6 +1391,10 @@ void checkUnknownAttrRestrictions(
13911391
/// it to later stages.
13921392
void bindSwitchCasePatternVars(DeclContext *dc, CaseStmt *stmt);
13931393

1394+
/// Add notes suggesting the addition of 'async' or '@asyncHandler', as
1395+
/// appropriate, to a diagnostic for a function that isn't an async context.
1396+
void addAsyncNotes(FuncDecl *func);
1397+
13941398
} // end namespace swift
13951399

13961400
#endif

test/attr/asynchandler.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// RUN: %target-swift-frontend -typecheck -verify %s -enable-experimental-concurrency
2+
3+
func globalAsyncFunction() async -> Int { 0 }
4+
5+
@asyncHandler func asyncHandler1() {
6+
// okay, it's an async context
7+
let _ = await globalAsyncFunction()
8+
}
9+
10+
@asyncHandler func asyncHandler2(fn: @autoclosure () async -> Int ) {
11+
// okay, it's an async context
12+
}
13+
14+
@asyncHandler
15+
func asyncHandlerBad1() -> Int { 0 }
16+
// expected-error@-1{{'@asyncHandler' function can only return 'Void'}}
17+
18+
@asyncHandler
19+
func asyncHandlerBad2() async { }
20+
// expected-error@-1{{'@asyncHandler' function cannot be 'async' itself}}{{25-31=}}
21+
22+
@asyncHandler
23+
func asyncHandlerBad3() throws { }
24+
// expected-error@-1{{'@asyncHandler' function cannot throw}}{{25-32=}}
25+
26+
@asyncHandler
27+
func asyncHandlerBad4(result: inout Int) { }
28+
// expected-error@-1{{'inout' parameter is not allowed in '@asyncHandler' function}}
29+
30+
struct X {
31+
@asyncHandler func asyncHandlerMethod() { }
32+
33+
@asyncHandler
34+
mutating func asyncHandlerMethodBad1() { }
35+
// expected-error@-1{{'@asyncHandler' function cannot be 'mutating'}}{{3-12=}}
36+
37+
@asyncHandler init() { }
38+
// expected-error@-1{{@asyncHandler may only be used on 'func' declarations}}
39+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// RUN: %target-swift-frontend -typecheck -verify %s
2+
3+
@asyncHandler func asyncHandler1() { }
4+
// expected-error@-1{{'@asyncHandler' is only valid when experimental concurrency is enabled}}

0 commit comments

Comments
 (0)