Skip to content

Commit 6ed662e

Browse files
committed
[Concurrency] @asyncHandler functions are an async context.
Allow async calls and await expressions within @asyncHandler functions. If we see an async call or await expression in a function that is not an async context, also suggest adding @asyncHandler if the function meets the semantic constraints.
1 parent 11d801d commit 6ed662e

File tree

8 files changed

+104
-47
lines changed

8 files changed

+104
-47
lines changed

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: 3 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,

lib/Sema/TypeCheckAttr.cpp

Lines changed: 71 additions & 36 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>
@@ -270,42 +330,7 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
270330
return;
271331
}
272332

273-
if (!func->getResultInterfaceType()->isVoid()) {
274-
func->diagnose(diag::asynchandler_returns_value)
275-
.highlight(func->getBodyResultTypeLoc().getSourceRange());
276-
attr->setInvalid();
277-
return;
278-
}
279-
280-
if (func->hasThrows()) {
281-
func->diagnose(diag::asynchandler_throws)
282-
.fixItRemove(func->getThrowsLoc());
283-
attr->setInvalid();
284-
return;
285-
}
286-
287-
if (func->hasAsync()) {
288-
func->diagnose(diag::asynchandler_async)
289-
.fixItRemove(func->getAsyncLoc());
290-
attr->setInvalid();
291-
return;
292-
}
293-
294-
for (auto param : *func->getParameters()) {
295-
if (param->isInOut()) {
296-
param->diagnose(diag::asynchandler_inout_parameter)
297-
.fixItRemove(param->getSpecifierLoc());
298-
attr->setInvalid();
299-
return;
300-
}
301-
}
302-
303-
if (func->isMutating()) {
304-
auto diag = Ctx.Diags.diagnose(func, diag::asynchandler_mutating);
305-
diag.highlight(attr->getRangeWithAt());
306-
if (auto mutatingAttr = func->getAttrs().getAttribute<MutatingAttr>()) {
307-
diag.fixItRemove(mutatingAttr->getRange());
308-
}
333+
if (checkAsyncHandler(func, /*diagnose=*/true)) {
309334
attr->setInvalid();
310335
return;
311336
}
@@ -5201,3 +5226,13 @@ void AttributeChecker::visitTransposeAttr(TransposeAttr *attr) {
52015226
// Set the resolved linearity parameter indices in the attribute.
52025227
attr->setParameterIndices(linearParamIndices);
52035228
}
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/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: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
// RUN: %target-swift-frontend -typecheck -verify %s -enable-experimental-concurrency
22

3-
@asyncHandler func asyncHandler1() { }
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+
}
413

514
@asyncHandler
615
func asyncHandlerBad1() -> Int { 0 }

test/expr/unary/async_await.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ func test2(
2121
}
2222

2323
func test3() { // expected-note{{add 'async' to function 'test3()' to make it asynchronous}}
24+
// expected-note@-1{{add '@asyncHandler' to function 'test3()' to create an implicit asynchronous context}}{{1-1=@asyncHandler }}
2425
_ = await getInt() // expected-error{{'async' in a function that does not support concurrency}}
2526
}
2627

@@ -36,7 +37,7 @@ struct SomeStruct {
3637
func acceptAutoclosureNonAsync(_: @autoclosure () -> Int) async { }
3738
func acceptAutoclosureAsync(_: @autoclosure () async -> Int) async { }
3839

39-
func acceptAutoclosureNonAsyncBad(_: @autoclosure () async -> Int) { }
40+
func acceptAutoclosureNonAsyncBad(_: @autoclosure () async -> Int) -> Int { 0 }
4041
// expected-error@-1{{'async' autoclosure parameter in a non-'async' function}}
4142
// expected-note@-2{{add 'async' to function 'acceptAutoclosureNonAsyncBad' to make it asynchronous}}
4243

@@ -46,13 +47,13 @@ struct HasAsyncBad {
4647
}
4748

4849
func testAutoclosure() async {
49-
await acceptAutoclosureAsync(getInt()) // expected-error{{call is 'async' in an autoclosure argument is not marked with 'await'}}
50+
await acceptAutoclosureAsync(getInt()) // expected-error{{call is 'async' in an autoclosure argument that is not marked with 'await'}}
5051
await acceptAutoclosureNonAsync(getInt()) // expected-error{{'async' in an autoclosure that does not support concurrency}}
5152

5253
await acceptAutoclosureAsync(await getInt())
5354
await acceptAutoclosureNonAsync(await getInt()) // expected-error{{'async' in an autoclosure that does not support concurrency}}
5455

55-
await acceptAutoclosureAsync(getInt()) // expected-error{{call is 'async' in an autoclosure argument is not marked with 'await'}}
56+
await acceptAutoclosureAsync(getInt()) // expected-error{{call is 'async' in an autoclosure argument that is not marked with 'await'}}
5657
await acceptAutoclosureNonAsync(getInt()) // expected-error{{'async' in an autoclosure that does not support concurrency}}
5758
}
5859

0 commit comments

Comments
 (0)