Skip to content

Commit 9b808c6

Browse files
committed
[Concurrency] Import "did" delegate methods as @asyncHandler.
Infer @asyncHandler on a protocol methods that follow the delegate convention of reporting that something happened via a "did" method, so long as they also meet the constraints for an @asyncHandler method in Swift. This enables inference of @asyncHandler for witnesses of these methods.
1 parent 63ed61d commit 9b808c6

File tree

11 files changed

+130
-13
lines changed

11 files changed

+130
-13
lines changed

include/swift/AST/Decl.h

+4
Original file line numberDiff line numberDiff line change
@@ -5948,6 +5948,10 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
59485948
/// Returns true if the function is an @asyncHandler.
59495949
bool isAsyncHandler() const;
59505950

5951+
/// Returns true if the function if the signature matches the form of an
5952+
/// @asyncHandler.
5953+
bool canBeAsyncHandler() const;
5954+
59515955
/// Returns true if the function body throws.
59525956
bool hasThrows() const { return Bits.AbstractFunctionDecl.Throws; }
59535957

include/swift/AST/DiagnosticsSema.def

+3
Original file line numberDiff line numberDiff line change
@@ -4121,6 +4121,9 @@ ERROR(asynchandler_async,none,
41214121
ERROR(asynchandler_inout_parameter,none,
41224122
"'inout' parameter is not allowed in '@asyncHandler' function",
41234123
())
4124+
ERROR(asynchandler_unsafe_pointer_parameter,none,
4125+
"'%0' parameter is not allowed in `@asyncHandler` function",
4126+
())
41244127
ERROR(asynchandler_mutating,none,
41254128
"'@asyncHandler' function cannot be 'mutating'",
41264129
())

include/swift/AST/TypeCheckRequests.h

+19
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,25 @@ class IsAsyncHandlerRequest :
797797
bool isCached() const { return true; }
798798
};
799799

800+
/// Determine whether the given function can be an @asyncHandler, without
801+
/// producing any diagnostics.
802+
class CanBeAsyncHandlerRequest :
803+
public SimpleRequest<CanBeAsyncHandlerRequest,
804+
bool(FuncDecl *),
805+
RequestFlags::Cached> {
806+
public:
807+
using SimpleRequest::SimpleRequest;
808+
809+
private:
810+
friend SimpleRequest;
811+
812+
bool evaluate(Evaluator &evaluator, FuncDecl *func) const;
813+
814+
public:
815+
// Caching
816+
bool isCached() const { return true; }
817+
};
818+
800819
/// Determine whether the given class is an actor.
801820
class IsActorRequest :
802821
public SimpleRequest<IsActorRequest,

include/swift/AST/TypeCheckerTypeIDZone.def

+2
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ SWIFT_REQUEST(TypeChecker, FunctionBuilderTypeRequest, Type(ValueDecl *),
8383
Cached, NoLocationInfo)
8484
SWIFT_REQUEST(TypeChecker, IsAsyncHandlerRequest, bool(FuncDecl *),
8585
Cached, NoLocationInfo)
86+
SWIFT_REQUEST(TypeChecker, CanBeAsyncHandlerRequest, bool(FuncDecl *),
87+
Cached, NoLocationInfo)
8688
SWIFT_REQUEST(TypeChecker, IsActorRequest, bool(ClassDecl *),
8789
Cached, NoLocationInfo)
8890
SWIFT_REQUEST(TypeChecker, ActorIsolationRequest,

lib/AST/Decl.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -6791,6 +6791,17 @@ bool AbstractFunctionDecl::isAsyncHandler() const {
67916791
false);
67926792
}
67936793

6794+
bool AbstractFunctionDecl::canBeAsyncHandler() const {
6795+
auto func = dyn_cast<FuncDecl>(this);
6796+
if (!func)
6797+
return false;
6798+
6799+
auto mutableFunc = const_cast<FuncDecl *>(func);
6800+
return evaluateOrDefault(getASTContext().evaluator,
6801+
CanBeAsyncHandlerRequest{mutableFunc},
6802+
false);
6803+
}
6804+
67946805
BraceStmt *AbstractFunctionDecl::getBody(bool canSynthesize) const {
67956806
if ((getBodyKind() == BodyKind::Synthesize ||
67966807
getBodyKind() == BodyKind::Unparsed) &&

lib/ClangImporter/ImportDecl.cpp

+59
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "swift/Basic/Defer.h"
4141
#include "swift/Basic/PrettyStackTrace.h"
4242
#include "swift/Basic/Statistic.h"
43+
#include "swift/Basic/StringExtras.h"
4344
#include "swift/ClangImporter/ClangModule.h"
4445
#include "swift/Config.h"
4546
#include "swift/Parse/Lexer.h"
@@ -7575,6 +7576,46 @@ bool importer::isSpecialUIKitStructZeroProperty(const clang::NamedDecl *decl) {
75757576
return ident->isStr("UIEdgeInsetsZero") || ident->isStr("UIOffsetZero");
75767577
}
75777578

7579+
/// Determine whether any of the parameters to the given function is of an
7580+
/// unsafe pointer type.
7581+
static bool hasAnyUnsafePointerParameters(FuncDecl *func) {
7582+
for (auto param : *func->getParameters()) {
7583+
Type paramType =
7584+
param->toFunctionParam().getPlainType()->lookThroughAllOptionalTypes();
7585+
if (paramType->getAnyPointerElementType()) {
7586+
return true;
7587+
}
7588+
}
7589+
7590+
return false;
7591+
}
7592+
7593+
/// Determine whether the given Objective-C method is likely to be an
7594+
/// asynchronous handler based on its name.
7595+
static bool isObjCMethodLikelyAsyncHandler(
7596+
const clang::ObjCMethodDecl *method) {
7597+
auto selector = method->getSelector();
7598+
7599+
for (unsigned argIdx : range(std::max(selector.getNumArgs(), 1u))) {
7600+
auto selectorPiece = selector.getNameForSlot(argIdx);
7601+
// For the first selector piece, look for the word "did" anywhere.
7602+
if (argIdx == 0) {
7603+
for (auto word : camel_case::getWords(selectorPiece)) {
7604+
if (word == "did" || word == "Did")
7605+
return true;
7606+
}
7607+
7608+
continue;
7609+
}
7610+
7611+
// Otherwise, check whether any subsequent selector piece starts with "did".
7612+
if (camel_case::getFirstWord(selectorPiece) == "did")
7613+
return true;
7614+
}
7615+
7616+
return false;
7617+
}
7618+
75787619
/// Import Clang attributes as Swift attributes.
75797620
void ClangImporter::Implementation::importAttributes(
75807621
const clang::NamedDecl *ClangDecl,
@@ -7813,6 +7854,24 @@ void ClangImporter::Implementation::importAttributes(
78137854
if (ClangDecl->hasAttr<clang::PureAttr>()) {
78147855
MappedDecl->getAttrs().add(new (C) EffectsAttr(EffectsKind::ReadOnly));
78157856
}
7857+
7858+
// Infer @asyncHandler on imported protocol methods that meet the semantic
7859+
// requirements.
7860+
if (SwiftContext.LangOpts.EnableExperimentalConcurrency) {
7861+
if (auto func = dyn_cast<FuncDecl>(MappedDecl)) {
7862+
if (auto proto = dyn_cast<ProtocolDecl>(func->getDeclContext())) {
7863+
if (proto->isObjC() && isa<clang::ObjCMethodDecl>(ClangDecl) &&
7864+
func->isInstanceMember() && !isa<AccessorDecl>(func) &&
7865+
isObjCMethodLikelyAsyncHandler(
7866+
cast<clang::ObjCMethodDecl>(ClangDecl)) &&
7867+
func->canBeAsyncHandler() &&
7868+
!hasAnyUnsafePointerParameters(func)) {
7869+
MappedDecl->getAttrs().add(
7870+
new (C) AsyncHandlerAttr(/*IsImplicit=*/false));
7871+
}
7872+
}
7873+
}
7874+
}
78167875
}
78177876

78187877
Decl *

lib/Sema/TypeCheckConcurrency.cpp

+15-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,14 @@
2222

2323
using namespace swift;
2424

25-
bool swift::checkAsyncHandler(FuncDecl *func, bool diagnose) {
25+
/// Check whether the @asyncHandler attribute can be applied to the given
26+
/// function declaration.
27+
///
28+
/// \param diagnose Whether to emit a diagnostic when a problem is encountered.
29+
///
30+
/// \returns \c true if there was a problem with adding the attribute, \c false
31+
/// otherwise.
32+
static bool checkAsyncHandler(FuncDecl *func, bool diagnose) {
2633
if (!func->getResultInterfaceType()->isVoid()) {
2734
if (diagnose) {
2835
func->diagnose(diag::asynchandler_returns_value)
@@ -78,7 +85,7 @@ bool swift::checkAsyncHandler(FuncDecl *func, bool diagnose) {
7885
void swift::addAsyncNotes(FuncDecl *func) {
7986
func->diagnose(diag::note_add_async_to_function, func->getName());
8087

81-
if (!checkAsyncHandler(func, /*diagnose=*/false)) {
88+
if (func->canBeAsyncHandler()) {
8289
func->diagnose(
8390
diag::note_add_asynchandler_to_function, func->getName())
8491
.fixItInsert(func->getAttributeInsertionLoc(false), "@asyncHandler ");
@@ -108,7 +115,7 @@ bool IsAsyncHandlerRequest::evaluate(
108115
return false;
109116

110117
// Is it possible to infer @asyncHandler for this function at all?
111-
if (checkAsyncHandler(func, /*diagnose=*/false))
118+
if (!func->canBeAsyncHandler())
112119
return false;
113120

114121
// Add an implicit @asyncHandler attribute and return true. We're done.
@@ -157,6 +164,11 @@ bool IsAsyncHandlerRequest::evaluate(
157164
return false;
158165
}
159166

167+
bool CanBeAsyncHandlerRequest::evaluate(
168+
Evaluator &evaluator, FuncDecl *func) const {
169+
return !checkAsyncHandler(func, /*diagnose=*/false);
170+
}
171+
160172
bool IsActorRequest::evaluate(
161173
Evaluator &evaluator, ClassDecl *classDecl) const {
162174
// If concurrency is not enabled, we don't have actors.

lib/Sema/TypeCheckConcurrency.h

-9
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,6 @@ class Expr;
2626
class FuncDecl;
2727
class ValueDecl;
2828

29-
/// Check whether the @asyncHandler attribute can be applied to the given
30-
/// function declaration.
31-
///
32-
/// \param diagnose Whether to emit a diagnostic when a problem is encountered.
33-
///
34-
/// \returns \c true if there was a problem with adding the attribute, \c false
35-
/// otherwise.
36-
bool checkAsyncHandler(FuncDecl *func, bool diagnose);
37-
3829
/// Add notes suggesting the addition of 'async' or '@asyncHandler', as
3930
/// appropriate, to a diagnostic for a function that isn't an async context.
4031
void addAsyncNotes(FuncDecl *func);

lib/Sema/TypeCheckProtocol.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -2458,7 +2458,7 @@ diagnoseMatch(ModuleDecl *module, NormalProtocolConformance *conformance,
24582458
bool canBeAsyncHandler = false;
24592459
if (auto witnessFunc = dyn_cast<FuncDecl>(match.Witness)) {
24602460
canBeAsyncHandler = !witnessFunc->isAsyncHandler() &&
2461-
!checkAsyncHandler(witnessFunc, /*diagnose=*/false);
2461+
witnessFunc->canBeAsyncHandler();
24622462
}
24632463
auto diag = match.Witness->diagnose(
24642464
canBeAsyncHandler ? diag::actor_isolated_witness_could_be_async_handler

test/IDE/print_clang_objc_async.swift

+8
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,11 @@
1818
// CHECK-DAG: func findAnswerFailingly() async throws -> String?
1919
// CHECK-DAG: func doSomethingFun(_ operation: String) async
2020
// CHECK: {{^[}]$}}
21+
22+
// CHECK-LABEL: protocol RefrigeratorDelegate
23+
// CHECK-NEXT: @asyncHandler func someoneDidOpenRefrigerator(_ fridge: Any)
24+
// CHECK-NEXT: @asyncHandler func refrigerator(_ fridge: Any, didGetFilledWithItems items: [Any])
25+
// CHECK-NEXT: {{^}} func refrigerator(_ fridge: Any, didGetFilledWithIntegers items: UnsafeMutablePointer<Int>, count: Int)
26+
// CHECK-NEXT: {{^}} func refrigerator(_ fridge: Any, willAddItem item: Any)
27+
// CHECK-NEXT: {{^}} func refrigerator(_ fridge: Any, didRemoveItem item: Any) -> Bool
28+
// CHECK-NEXT: {{^[}]$}}

test/Inputs/clang-importer-sdk/usr/include/ObjCConcurrency.h

+8
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,12 @@
1313
@property(readwrite) void (^completionHandler)(NSInteger);
1414
@end
1515

16+
@protocol RefrigeratorDelegate<NSObject>
17+
- (void)someoneDidOpenRefrigerator:(id)fridge;
18+
- (void)refrigerator:(id)fridge didGetFilledWithItems:(NSArray *)items;
19+
- (void)refrigerator:(id)fridge didGetFilledWithIntegers:(NSInteger *)items count:(NSInteger)count;
20+
- (void)refrigerator:(id)fridge willAddItem:(id)item;
21+
- (BOOL)refrigerator:(id)fridge didRemoveItem:(id)item;
22+
@end
23+
1624
#pragma clang assume_nonnull end

0 commit comments

Comments
 (0)