Skip to content

Commit 4b1b0bb

Browse files
authored
Merge pull request #34065 from DougGregor/concurrency-objc-asynchandler-inference
[Concurrency] Import "did" delegate methods as @asyncHandler.
2 parents d953fa6 + a000375 commit 4b1b0bb

File tree

10 files changed

+127
-13
lines changed

10 files changed

+127
-13
lines changed

include/swift/AST/Decl.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5950,6 +5950,10 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
59505950
/// Returns true if the function is an @asyncHandler.
59515951
bool isAsyncHandler() const;
59525952

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

include/swift/AST/TypeCheckRequests.h

Lines changed: 19 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6792,6 +6792,17 @@ bool AbstractFunctionDecl::isAsyncHandler() const {
67926792
false);
67936793
}
67946794

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

lib/ClangImporter/ImportDecl.cpp

Lines changed: 59 additions & 0 deletions
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

Lines changed: 15 additions & 3 deletions
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

Lines changed: 0 additions & 9 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 8 additions & 0 deletions
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

Lines changed: 8 additions & 0 deletions
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)