Skip to content

[Concurrency] Import "did" delegate methods as @asyncHandler. #34065

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -5948,6 +5948,10 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
/// Returns true if the function is an @asyncHandler.
bool isAsyncHandler() const;

/// Returns true if the function if the signature matches the form of an
/// @asyncHandler.
bool canBeAsyncHandler() const;

/// Returns true if the function body throws.
bool hasThrows() const { return Bits.AbstractFunctionDecl.Throws; }

Expand Down
19 changes: 19 additions & 0 deletions include/swift/AST/TypeCheckRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,25 @@ class IsAsyncHandlerRequest :
bool isCached() const { return true; }
};

/// Determine whether the given function can be an @asyncHandler, without
/// producing any diagnostics.
class CanBeAsyncHandlerRequest :
public SimpleRequest<CanBeAsyncHandlerRequest,
bool(FuncDecl *),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;

private:
friend SimpleRequest;

bool evaluate(Evaluator &evaluator, FuncDecl *func) const;

public:
// Caching
bool isCached() const { return true; }
};

/// Determine whether the given class is an actor.
class IsActorRequest :
public SimpleRequest<IsActorRequest,
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/TypeCheckerTypeIDZone.def
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ SWIFT_REQUEST(TypeChecker, FunctionBuilderTypeRequest, Type(ValueDecl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, IsAsyncHandlerRequest, bool(FuncDecl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, CanBeAsyncHandlerRequest, bool(FuncDecl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, IsActorRequest, bool(ClassDecl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, ActorIsolationRequest,
Expand Down
11 changes: 11 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6791,6 +6791,17 @@ bool AbstractFunctionDecl::isAsyncHandler() const {
false);
}

bool AbstractFunctionDecl::canBeAsyncHandler() const {
auto func = dyn_cast<FuncDecl>(this);
if (!func)
return false;

auto mutableFunc = const_cast<FuncDecl *>(func);
return evaluateOrDefault(getASTContext().evaluator,
CanBeAsyncHandlerRequest{mutableFunc},
false);
}

BraceStmt *AbstractFunctionDecl::getBody(bool canSynthesize) const {
if ((getBodyKind() == BodyKind::Synthesize ||
getBodyKind() == BodyKind::Unparsed) &&
Expand Down
59 changes: 59 additions & 0 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include "swift/Basic/Defer.h"
#include "swift/Basic/PrettyStackTrace.h"
#include "swift/Basic/Statistic.h"
#include "swift/Basic/StringExtras.h"
#include "swift/ClangImporter/ClangModule.h"
#include "swift/Config.h"
#include "swift/Parse/Lexer.h"
Expand Down Expand Up @@ -7575,6 +7576,46 @@ bool importer::isSpecialUIKitStructZeroProperty(const clang::NamedDecl *decl) {
return ident->isStr("UIEdgeInsetsZero") || ident->isStr("UIOffsetZero");
}

/// Determine whether any of the parameters to the given function is of an
/// unsafe pointer type.
static bool hasAnyUnsafePointerParameters(FuncDecl *func) {
for (auto param : *func->getParameters()) {
Type paramType =
param->toFunctionParam().getPlainType()->lookThroughAllOptionalTypes();
if (paramType->getAnyPointerElementType()) {
return true;
}
}

return false;
}

/// Determine whether the given Objective-C method is likely to be an
/// asynchronous handler based on its name.
static bool isObjCMethodLikelyAsyncHandler(
const clang::ObjCMethodDecl *method) {
auto selector = method->getSelector();

for (unsigned argIdx : range(std::max(selector.getNumArgs(), 1u))) {
auto selectorPiece = selector.getNameForSlot(argIdx);
// For the first selector piece, look for the word "did" anywhere.
if (argIdx == 0) {
for (auto word : camel_case::getWords(selectorPiece)) {
if (word == "did" || word == "Did")
return true;
}

continue;
}

// Otherwise, check whether any subsequent selector piece starts with "did".
if (camel_case::getFirstWord(selectorPiece) == "did")
return true;
}

return false;
}

/// Import Clang attributes as Swift attributes.
void ClangImporter::Implementation::importAttributes(
const clang::NamedDecl *ClangDecl,
Expand Down Expand Up @@ -7813,6 +7854,24 @@ void ClangImporter::Implementation::importAttributes(
if (ClangDecl->hasAttr<clang::PureAttr>()) {
MappedDecl->getAttrs().add(new (C) EffectsAttr(EffectsKind::ReadOnly));
}

// Infer @asyncHandler on imported protocol methods that meet the semantic
// requirements.
if (SwiftContext.LangOpts.EnableExperimentalConcurrency) {
if (auto func = dyn_cast<FuncDecl>(MappedDecl)) {
if (auto proto = dyn_cast<ProtocolDecl>(func->getDeclContext())) {
if (proto->isObjC() && isa<clang::ObjCMethodDecl>(ClangDecl) &&
func->isInstanceMember() && !isa<AccessorDecl>(func) &&
isObjCMethodLikelyAsyncHandler(
cast<clang::ObjCMethodDecl>(ClangDecl)) &&
func->canBeAsyncHandler() &&
!hasAnyUnsafePointerParameters(func)) {
MappedDecl->getAttrs().add(
new (C) AsyncHandlerAttr(/*IsImplicit=*/false));
}
}
}
}
}

Decl *
Expand Down
18 changes: 15 additions & 3 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,14 @@

using namespace swift;

bool swift::checkAsyncHandler(FuncDecl *func, bool diagnose) {
/// Check whether the @asyncHandler attribute can be applied to the given
/// function declaration.
///
/// \param diagnose Whether to emit a diagnostic when a problem is encountered.
///
/// \returns \c true if there was a problem with adding the attribute, \c false
/// otherwise.
static bool checkAsyncHandler(FuncDecl *func, bool diagnose) {
if (!func->getResultInterfaceType()->isVoid()) {
if (diagnose) {
func->diagnose(diag::asynchandler_returns_value)
Expand Down Expand Up @@ -78,7 +85,7 @@ bool swift::checkAsyncHandler(FuncDecl *func, bool diagnose) {
void swift::addAsyncNotes(FuncDecl *func) {
func->diagnose(diag::note_add_async_to_function, func->getName());

if (!checkAsyncHandler(func, /*diagnose=*/false)) {
if (func->canBeAsyncHandler()) {
func->diagnose(
diag::note_add_asynchandler_to_function, func->getName())
.fixItInsert(func->getAttributeInsertionLoc(false), "@asyncHandler ");
Expand Down Expand Up @@ -108,7 +115,7 @@ bool IsAsyncHandlerRequest::evaluate(
return false;

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

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

bool CanBeAsyncHandlerRequest::evaluate(
Evaluator &evaluator, FuncDecl *func) const {
return !checkAsyncHandler(func, /*diagnose=*/false);
}

bool IsActorRequest::evaluate(
Evaluator &evaluator, ClassDecl *classDecl) const {
// If concurrency is not enabled, we don't have actors.
Expand Down
9 changes: 0 additions & 9 deletions lib/Sema/TypeCheckConcurrency.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,6 @@ class Expr;
class FuncDecl;
class ValueDecl;

/// Check whether the @asyncHandler attribute can be applied to the given
/// function declaration.
///
/// \param diagnose Whether to emit a diagnostic when a problem is encountered.
///
/// \returns \c true if there was a problem with adding the attribute, \c false
/// otherwise.
bool checkAsyncHandler(FuncDecl *func, bool diagnose);

/// Add notes suggesting the addition of 'async' or '@asyncHandler', as
/// appropriate, to a diagnostic for a function that isn't an async context.
void addAsyncNotes(FuncDecl *func);
Expand Down
2 changes: 1 addition & 1 deletion lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2458,7 +2458,7 @@ diagnoseMatch(ModuleDecl *module, NormalProtocolConformance *conformance,
bool canBeAsyncHandler = false;
if (auto witnessFunc = dyn_cast<FuncDecl>(match.Witness)) {
canBeAsyncHandler = !witnessFunc->isAsyncHandler() &&
!checkAsyncHandler(witnessFunc, /*diagnose=*/false);
witnessFunc->canBeAsyncHandler();
}
auto diag = match.Witness->diagnose(
canBeAsyncHandler ? diag::actor_isolated_witness_could_be_async_handler
Expand Down
8 changes: 8 additions & 0 deletions test/IDE/print_clang_objc_async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,11 @@
// CHECK-DAG: func findAnswerFailingly() async throws -> String?
// CHECK-DAG: func doSomethingFun(_ operation: String) async
// CHECK: {{^[}]$}}

// CHECK-LABEL: protocol RefrigeratorDelegate
// CHECK-NEXT: @asyncHandler func someoneDidOpenRefrigerator(_ fridge: Any)
// CHECK-NEXT: @asyncHandler func refrigerator(_ fridge: Any, didGetFilledWithItems items: [Any])
// CHECK-NEXT: {{^}} func refrigerator(_ fridge: Any, didGetFilledWithIntegers items: UnsafeMutablePointer<Int>, count: Int)
// CHECK-NEXT: {{^}} func refrigerator(_ fridge: Any, willAddItem item: Any)
// CHECK-NEXT: {{^}} func refrigerator(_ fridge: Any, didRemoveItem item: Any) -> Bool
// CHECK-NEXT: {{^[}]$}}
8 changes: 8 additions & 0 deletions test/Inputs/clang-importer-sdk/usr/include/ObjCConcurrency.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,12 @@
@property(readwrite) void (^completionHandler)(NSInteger);
@end

@protocol RefrigeratorDelegate<NSObject>
- (void)someoneDidOpenRefrigerator:(id)fridge;
- (void)refrigerator:(id)fridge didGetFilledWithItems:(NSArray *)items;
- (void)refrigerator:(id)fridge didGetFilledWithIntegers:(NSInteger *)items count:(NSInteger)count;
- (void)refrigerator:(id)fridge willAddItem:(id)item;
- (BOOL)refrigerator:(id)fridge didRemoveItem:(id)item;
@end

#pragma clang assume_nonnull end