Skip to content

Commit 84a21d1

Browse files
authored
Merge pull request swiftlang#33488 from DougGregor/concurrency-infer-asynchandler
[Concurrency] Infer @asyncHandler from protocol requirements.
2 parents 60a5780 + 5a0f3c4 commit 84a21d1

File tree

8 files changed

+214
-75
lines changed

8 files changed

+214
-75
lines changed

include/swift/AST/Decl.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5892,9 +5892,12 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
58925892
///
58935893
/// Functions that are an 'async' context can make calls to 'async' functions.
58945894
bool isAsyncContext() const {
5895-
return hasAsync() || getAttrs().hasAttribute<AsyncHandlerAttr>();
5895+
return hasAsync() || isAsyncHandler();
58965896
}
58975897

5898+
/// Returns true if the function is an @asyncHandler.
5899+
bool isAsyncHandler() const;
5900+
58985901
/// Returns true if the function body throws.
58995902
bool hasThrows() const { return Bits.AbstractFunctionDecl.Throws; }
59005903

include/swift/AST/TypeCheckRequests.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,24 @@ class SelfAccessKindRequest :
781781
void cacheResult(SelfAccessKind value) const;
782782
};
783783

784+
/// Determine whether the given function is an @asyncHandler.
785+
class IsAsyncHandlerRequest :
786+
public SimpleRequest<IsAsyncHandlerRequest,
787+
bool(FuncDecl *),
788+
RequestFlags::Cached> {
789+
public:
790+
using SimpleRequest::SimpleRequest;
791+
792+
private:
793+
friend SimpleRequest;
794+
795+
bool evaluate(Evaluator &evaluator, FuncDecl *func) const;
796+
797+
public:
798+
// Caching
799+
bool isCached() const { return true; }
800+
};
801+
784802
/// Request whether the storage has a mutating getter.
785803
class IsGetterMutatingRequest :
786804
public SimpleRequest<IsGetterMutatingRequest,

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ SWIFT_REQUEST(TypeChecker, ExtendedTypeRequest, Type(ExtensionDecl *), Cached,
8181
NoLocationInfo)
8282
SWIFT_REQUEST(TypeChecker, FunctionBuilderTypeRequest, Type(ValueDecl *),
8383
Cached, NoLocationInfo)
84+
SWIFT_REQUEST(TypeChecker, IsAsyncHandlerRequest, bool(FuncDecl *),
85+
Cached, NoLocationInfo)
8486
SWIFT_REQUEST(TypeChecker, FunctionOperatorRequest, OperatorDecl *(FuncDecl *),
8587
Cached, NoLocationInfo)
8688
SWIFT_REQUEST(NameLookup, GenericSignatureRequest,

lib/AST/Decl.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6691,6 +6691,17 @@ bool AbstractFunctionDecl::argumentNameIsAPIByDefault() const {
66916691
return false;
66926692
}
66936693

6694+
bool AbstractFunctionDecl::isAsyncHandler() const {
6695+
auto func = dyn_cast<FuncDecl>(this);
6696+
if (!func)
6697+
return false;
6698+
6699+
auto mutableFunc = const_cast<FuncDecl *>(func);
6700+
return evaluateOrDefault(getASTContext().evaluator,
6701+
IsAsyncHandlerRequest{mutableFunc},
6702+
false);
6703+
}
6704+
66946705
BraceStmt *AbstractFunctionDecl::getBody(bool canSynthesize) const {
66956706
if ((getBodyKind() == BodyKind::Synthesize ||
66966707
getBodyKind() == BodyKind::Unparsed) &&

lib/Sema/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ add_swift_host_library(swiftSema STATIC
4141
TypeCheckCaptures.cpp
4242
TypeCheckCircularity.cpp
4343
TypeCheckCodeCompletion.cpp
44+
TypeCheckConcurrency.cpp
4445
TypeCheckConstraints.cpp
4546
TypeCheckDecl.cpp
4647
TypeCheckDeclObjC.cpp

lib/Sema/TypeCheckAttr.cpp

Lines changed: 2 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -41,66 +41,6 @@
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-
10444
namespace {
10545
/// This emits a diagnostic with a fixit to remove the attribute.
10646
template<typename ...ArgTypes>
@@ -330,10 +270,8 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
330270
return;
331271
}
332272

333-
if (checkAsyncHandler(func, /*diagnose=*/true)) {
334-
attr->setInvalid();
335-
return;
336-
}
273+
// Trigger the request to check for @asyncHandler.
274+
(void)func->isAsyncHandler();
337275
}
338276
};
339277
} // end anonymous namespace
@@ -5226,13 +5164,3 @@ void AttributeChecker::visitTransposeAttr(TransposeAttr *attr) {
52265164
// Set the resolved linearity parameter indices in the attribute.
52275165
attr->setParameterIndices(linearParamIndices);
52285166
}
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/TypeCheckConcurrency.cpp

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
//===--- TypeCheckConcurrency.cpp - Concurrency ---------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// This file implements type checking support for Swift's concurrency model.
14+
//
15+
//===----------------------------------------------------------------------===//
16+
#include "TypeChecker.h"
17+
#include "swift/AST/ParameterList.h"
18+
#include "swift/AST/ProtocolConformance.h"
19+
#include "swift/AST/TypeCheckRequests.h"
20+
21+
using namespace swift;
22+
23+
/// Check whether the @asyncHandler attribute can be applied to the given
24+
/// function declaration.
25+
///
26+
/// \param diagnose Whether to emit a diagnostic when a problem is encountered.
27+
///
28+
/// \returns \c true if there was a problem with adding the attribute, \c false
29+
/// otherwise.
30+
static bool checkAsyncHandler(FuncDecl *func, bool diagnose) {
31+
if (!func->getResultInterfaceType()->isVoid()) {
32+
if (diagnose) {
33+
func->diagnose(diag::asynchandler_returns_value)
34+
.highlight(func->getBodyResultTypeLoc().getSourceRange());
35+
}
36+
37+
return true;
38+
}
39+
40+
if (func->hasThrows()) {
41+
if (diagnose) {
42+
func->diagnose(diag::asynchandler_throws)
43+
.fixItRemove(func->getThrowsLoc());
44+
}
45+
46+
return true;
47+
}
48+
49+
if (func->hasAsync()) {
50+
if (diagnose) {
51+
func->diagnose(diag::asynchandler_async)
52+
.fixItRemove(func->getAsyncLoc());
53+
}
54+
55+
return true;
56+
}
57+
58+
for (auto param : *func->getParameters()) {
59+
if (param->isInOut()) {
60+
if (diagnose) {
61+
param->diagnose(diag::asynchandler_inout_parameter)
62+
.fixItRemove(param->getSpecifierLoc());
63+
}
64+
65+
return true;
66+
}
67+
}
68+
69+
if (func->isMutating()) {
70+
if (diagnose) {
71+
auto diag = func->diagnose(diag::asynchandler_mutating);
72+
if (auto mutatingAttr = func->getAttrs().getAttribute<MutatingAttr>()) {
73+
diag.fixItRemove(mutatingAttr->getRange());
74+
}
75+
}
76+
77+
return true;
78+
}
79+
80+
return false;
81+
}
82+
83+
void swift::addAsyncNotes(FuncDecl *func) {
84+
func->diagnose(diag::note_add_async_to_function, func->getName());
85+
86+
if (!checkAsyncHandler(func, /*diagnose=*/false)) {
87+
func->diagnose(
88+
diag::note_add_asynchandler_to_function, func->getName())
89+
.fixItInsert(func->getAttributeInsertionLoc(false), "@asyncHandler ");
90+
}
91+
}
92+
93+
bool IsAsyncHandlerRequest::evaluate(
94+
Evaluator &evaluator, FuncDecl *func) const {
95+
// Check whether the attribute was explicitly specified.
96+
if (auto attr = func->getAttrs().getAttribute<AsyncHandlerAttr>()) {
97+
// Check for well-formedness.
98+
if (checkAsyncHandler(func, /*diagnose=*/true)) {
99+
attr->setInvalid();
100+
return false;
101+
}
102+
103+
return true;
104+
}
105+
106+
if (!func->getASTContext().LangOpts.EnableExperimentalConcurrency)
107+
return false;
108+
109+
// Are we in a context where inference is possible?
110+
auto dc = func->getDeclContext();
111+
if (!dc->isTypeContext() || !dc->getParentSourceFile() ||
112+
isa<ProtocolDecl>(dc) || !func->hasBody())
113+
return false;
114+
115+
// Is it possible to infer @asyncHandler for this function at all?
116+
if (checkAsyncHandler(func, /*diagnose=*/false))
117+
return false;
118+
119+
// Add an implicit @asyncHandler attribute and return true. We're done.
120+
auto addImplicitAsyncHandlerAttr = [&] {
121+
func->getAttrs().add(new (func->getASTContext()) AsyncHandlerAttr(true));
122+
return true;
123+
};
124+
125+
// Check whether any of the conformances in the context of the function
126+
// implies @asyncHandler.
127+
{
128+
auto idc = cast<IterableDeclContext>(dc->getAsDecl());
129+
auto conformances = evaluateOrDefault(
130+
dc->getASTContext().evaluator,
131+
LookupAllConformancesInContextRequest{idc}, { });
132+
133+
for (auto conformance : conformances) {
134+
auto protocol = conformance->getProtocol();
135+
for (auto found : protocol->lookupDirect(func->getName())) {
136+
if (!isa<ProtocolDecl>(found->getDeclContext()))
137+
continue;
138+
139+
auto requirement = dyn_cast<FuncDecl>(found);
140+
if (!requirement)
141+
continue;
142+
143+
if (!requirement->isAsyncHandler())
144+
continue;
145+
146+
auto witness = conformance->getWitnessDecl(requirement);
147+
if (witness != func)
148+
continue;
149+
150+
return addImplicitAsyncHandlerAttr();
151+
}
152+
}
153+
}
154+
155+
// Look through dynamic replacements.
156+
if (auto replaced = func->getDynamicallyReplacedDecl()) {
157+
if (auto replacedFunc = dyn_cast<FuncDecl>(replaced))
158+
if (replacedFunc->isAsyncHandler())
159+
return addImplicitAsyncHandlerAttr();
160+
}
161+
162+
return false;
163+
}

test/attr/asynchandler.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,16 @@ struct X {
3737
@asyncHandler init() { }
3838
// expected-error@-1{{@asyncHandler may only be used on 'func' declarations}}
3939
}
40+
41+
42+
// Inference of @asyncHandler
43+
protocol P {
44+
@asyncHandler func callback()
45+
}
46+
47+
extension X: P {
48+
func callback() {
49+
// okay, it's an async context
50+
let _ = await globalAsyncFunction()
51+
}
52+
}

0 commit comments

Comments
 (0)