Skip to content

Commit 1e5d30f

Browse files
committed
[Concurrency] Import Objective-C methods with completion handlers as async
When a given Objective-C method has a completion handler parameter with an appropriate signature, import that Objective-C method as async. For example, consider the following CloudKit API: - (void)fetchShareParticipantWithUserRecordID:(CKRecordID *)userRecordID completionHandler:(void (^)(CKShareParticipant * _Nullable shareParticipant, NSError * _Nullable error))completionHandler; With the experimental concurrency model, this would import as: func fetchShareParticipant(withUserRecordID userRecordID: CKRecord.ID) async throws -> CKShare.Participant? The compiler will be responsible for turning the caller's continuation into a block to pass along to the completion handler. When the error parameter of the completion handler is non-null, the async call will result in that error being thrown. Otherwise, the other arguments passed to that completion handler will be returned as the result of the async call. async versions of methods are imported alongside their completion-handler versions, to maintain source compatibility with existing code that provides a completion handler. Note that this only covers the Clang importer portion of this task.
1 parent 16876fb commit 1e5d30f

File tree

16 files changed

+564
-23
lines changed

16 files changed

+564
-23
lines changed

include/swift/AST/Decl.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ namespace swift {
6363
class Type;
6464
class Expr;
6565
class DeclRefExpr;
66+
class ForeignAsyncConvention;
6667
class ForeignErrorConvention;
6768
class LiteralExpr;
6869
class BraceStmt;
@@ -6148,7 +6149,15 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
61486149
/// being dropped altogether. `None` is returned for a normal function
61496150
/// or method.
61506151
Optional<int> getForeignFunctionAsMethodSelfParameterIndex() const;
6151-
6152+
6153+
/// Set information about the foreign async convention used by this
6154+
/// declaration.
6155+
void setForeignAsyncConvention(const ForeignAsyncConvention &convention);
6156+
6157+
/// Get information about the foreign async convention used by this
6158+
/// declaration, given that it is @objc and 'async'.
6159+
Optional<ForeignAsyncConvention> getForeignAsyncConvention() const;
6160+
61526161
static bool classof(const Decl *D) {
61536162
return D->getKind() >= DeclKind::First_AbstractFunctionDecl &&
61546163
D->getKind() <= DeclKind::Last_AbstractFunctionDecl;
@@ -6277,7 +6286,8 @@ class FuncDecl : public AbstractFunctionDecl {
62776286
DeclContext *Parent);
62786287

62796288
static FuncDecl *createImported(ASTContext &Context, SourceLoc FuncLoc,
6280-
DeclName Name, SourceLoc NameLoc, bool Throws,
6289+
DeclName Name, SourceLoc NameLoc,
6290+
bool Async, bool Throws,
62816291
ParameterList *BodyParams, Type FnRetType,
62826292
DeclContext *Parent, ClangNode ClangN);
62836293

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//===--- ForeignAsyncConvention.h - Async conventions -----------*- C++ -*-===//
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 defines the ForeignAsyncConvention structure, which
14+
// describes the rules for how to detect that a foreign API is asynchronous.
15+
//
16+
//===----------------------------------------------------------------------===//
17+
18+
#ifndef SWIFT_FOREIGN_ASYNC_CONVENTION_H
19+
#define SWIFT_FOREIGN_ASYNC_CONVENTION_H
20+
21+
#include "swift/AST/Type.h"
22+
23+
namespace swift {
24+
25+
/// A small structure describing the async convention of a foreign declaration.
26+
class ForeignAsyncConvention {
27+
/// The index of the completion handler parameters.
28+
unsigned CompletionHandlerParamIndex;
29+
30+
/// When non-zero, indicates which parameter to the completion handler is the
31+
/// Error? parameter (minus one) that makes this async function also throwing.
32+
unsigned CompletionHandlerErrorParamIndex;
33+
public:
34+
ForeignAsyncConvention()
35+
: CompletionHandlerParamIndex(0), CompletionHandlerErrorParamIndex(0) { }
36+
37+
ForeignAsyncConvention(unsigned completionHandlerParamIndex,
38+
Optional<unsigned> completionHandlerErrorParamIndex)
39+
: CompletionHandlerParamIndex(completionHandlerParamIndex),
40+
CompletionHandlerErrorParamIndex(
41+
completionHandlerErrorParamIndex
42+
? *completionHandlerErrorParamIndex + 1
43+
: 0) {}
44+
45+
/// Retrieve the index of the completion handler parameter, which will be
46+
/// erased from the Swift signature of the imported async function.
47+
unsigned completionHandlerParamIndex() const {
48+
return CompletionHandlerParamIndex;
49+
}
50+
51+
/// Retrieve the index of the \c Error? parameter in the completion handler's
52+
/// parameter list. When argument passed to this parameter is non-null, the
53+
/// provided error will be thrown by the async function.
54+
Optional<unsigned> completionHandlerErrorParamIndex() const {
55+
if (CompletionHandlerErrorParamIndex == 0)
56+
return None;
57+
58+
return CompletionHandlerErrorParamIndex - 1;
59+
}
60+
61+
/// Whether the async function is throwing due to the completion handler
62+
/// having an \c Error? parameter.
63+
///
64+
/// Equivalent to \c static_cast<bool>(completionHandlerErrorParamIndex()).
65+
bool isThrowing() const {
66+
return CompletionHandlerErrorParamIndex != 0;
67+
}
68+
69+
bool operator==(ForeignAsyncConvention other) const {
70+
return CompletionHandlerParamIndex == other.CompletionHandlerParamIndex
71+
&& CompletionHandlerErrorParamIndex ==
72+
other.CompletionHandlerErrorParamIndex;
73+
}
74+
bool operator!=(ForeignAsyncConvention other) const {
75+
return !(*this == other);
76+
}
77+
};
78+
79+
}
80+
81+
#endif

include/swift/AST/ForeignErrorConvention.h

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ class ForeignErrorConvention {
8181
}
8282

8383
Info() = default;
84+
85+
Kind getKind() const {
86+
return static_cast<Kind>(TheKind);
87+
}
8488
};
8589

8690
private:
@@ -178,11 +182,24 @@ class ForeignErrorConvention {
178182
/// Returns the physical result type of the function, for functions
179183
/// that completely erase this information.
180184
CanType getResultType() const {
181-
assert(getKind() == ZeroResult ||
182-
getKind() == NonZeroResult);
185+
assert(resultTypeErasedToVoid(getKind()));
183186
return ResultType;
184187
}
185-
188+
189+
/// Whether this kind of error import erases the result type to 'Void'.
190+
static bool resultTypeErasedToVoid(Kind kind) {
191+
switch (kind) {
192+
case ZeroResult:
193+
case NonZeroResult:
194+
return true;
195+
196+
case ZeroPreservedResult:
197+
case NilResult:
198+
case NonNilError:
199+
return false;
200+
}
201+
}
202+
186203
bool operator==(ForeignErrorConvention other) const {
187204
return info.TheKind == other.info.TheKind
188205
&& info.ErrorIsOwned == other.info.ErrorIsOwned

lib/AST/ASTContext.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "ClangTypeConverter.h"
1919
#include "ForeignRepresentationInfo.h"
2020
#include "SubstitutionMapStorage.h"
21+
#include "swift/AST/ForeignAsyncConvention.h"
2122
#include "swift/AST/ClangModuleLoader.h"
2223
#include "swift/AST/ConcreteDeclRef.h"
2324
#include "swift/AST/DiagnosticEngine.h"
@@ -281,6 +282,10 @@ struct ASTContext::Implementation {
281282
llvm::DenseMap<const AbstractFunctionDecl *,
282283
ForeignErrorConvention> ForeignErrorConventions;
283284

285+
/// Map from declarations to foreign async conventions.
286+
llvm::DenseMap<const AbstractFunctionDecl *,
287+
ForeignAsyncConvention> ForeignAsyncConventions;
288+
284289
/// Cache of previously looked-up precedence queries.
285290
AssociativityCacheType AssociativityCache;
286291

@@ -2238,6 +2243,24 @@ AbstractFunctionDecl::getForeignErrorConvention() const {
22382243
return it->second;
22392244
}
22402245

2246+
void AbstractFunctionDecl::setForeignAsyncConvention(
2247+
const ForeignAsyncConvention &conv) {
2248+
assert(hasAsync() && "setting error convention on non-throwing decl");
2249+
auto &conventionsMap = getASTContext().getImpl().ForeignAsyncConventions;
2250+
assert(!conventionsMap.count(this) && "error convention already set");
2251+
conventionsMap.insert({this, conv});
2252+
}
2253+
2254+
Optional<ForeignAsyncConvention>
2255+
AbstractFunctionDecl::getForeignAsyncConvention() const {
2256+
if (!hasAsync())
2257+
return None;
2258+
auto &conventionsMap = getASTContext().getImpl().ForeignAsyncConventions;
2259+
auto it = conventionsMap.find(this);
2260+
if (it == conventionsMap.end()) return None;
2261+
return it->second;
2262+
}
2263+
22412264
Optional<KnownFoundationEntity> swift::getKnownFoundationEntity(StringRef name){
22422265
return llvm::StringSwitch<Optional<KnownFoundationEntity>>(name)
22432266
#define FOUNDATION_ENTITY(Name) .Case(#Name, KnownFoundationEntity::Name)

lib/AST/Decl.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7255,13 +7255,14 @@ FuncDecl *FuncDecl::createImplicit(ASTContext &Context,
72557255

72567256
FuncDecl *FuncDecl::createImported(ASTContext &Context, SourceLoc FuncLoc,
72577257
DeclName Name, SourceLoc NameLoc,
7258-
bool Throws, ParameterList *BodyParams,
7258+
bool Async, bool Throws,
7259+
ParameterList *BodyParams,
72597260
Type FnRetType, DeclContext *Parent,
72607261
ClangNode ClangN) {
72617262
assert(ClangN && FnRetType);
72627263
auto *const FD = FuncDecl::createImpl(
72637264
Context, SourceLoc(), StaticSpellingKind::None, FuncLoc, Name, NameLoc,
7264-
/*Async=*/false, SourceLoc(), Throws, SourceLoc(),
7265+
Async, SourceLoc(), Throws, SourceLoc(),
72657266
/*GenericParams=*/nullptr, Parent, ClangN);
72667267
FD->setParameters(BodyParams);
72677268
FD->setResultInterfaceType(FnRetType);

lib/ClangImporter/ClangImporter.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3681,6 +3681,7 @@ void ClangImporter::Implementation::lookupValue(
36813681
clangDecl->getMostRecentDecl();
36823682

36833683
CurrentVersion.forEachOtherImportNameVersion(
3684+
SwiftContext.LangOpts.EnableExperimentalConcurrency,
36843685
[&](ImportNameVersion nameVersion) {
36853686
if (anyMatching)
36863687
return;
@@ -3690,6 +3691,12 @@ void ClangImporter::Implementation::lookupValue(
36903691
if (!newName.getDeclName().matchesRef(name))
36913692
return;
36923693

3694+
// If we asked for an async import and didn't find one, skip this.
3695+
// This filters out duplicates.
3696+
if (nameVersion.supportsConcurrency() &&
3697+
!newName.getAsyncInfo())
3698+
return;
3699+
36933700
const clang::DeclContext *clangDC =
36943701
newName.getEffectiveContext().getAsDeclContext();
36953702
if (!clangDC || !clangDC->isFileContext())

lib/ClangImporter/ImportDecl.cpp

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ static FuncDecl *createFuncOrAccessor(ASTContext &ctx, SourceLoc funcLoc,
160160
DeclName name, SourceLoc nameLoc,
161161
ParameterList *bodyParams,
162162
Type resultTy,
163+
bool async,
163164
bool throws,
164165
DeclContext *dc,
165166
ClangNode clangNode) {
@@ -176,7 +177,7 @@ static FuncDecl *createFuncOrAccessor(ASTContext &ctx, SourceLoc funcLoc,
176177
bodyParams,
177178
resultTy, dc, clangNode);
178179
} else {
179-
return FuncDecl::createImported(ctx, funcLoc, name, nameLoc, throws,
180+
return FuncDecl::createImported(ctx, funcLoc, name, nameLoc, async, throws,
180181
bodyParams, resultTy, dc, clangNode);
181182
}
182183
}
@@ -2254,7 +2255,7 @@ namespace {
22542255
/// Whether the names we're importing are from the language version the user
22552256
/// requested, or if these are decls from another version
22562257
bool isActiveSwiftVersion() const {
2257-
return getVersion() == getActiveSwiftVersion();
2258+
return getVersion().withConcurrency(false) == getActiveSwiftVersion().withConcurrency(false);
22582259
}
22592260

22602261
void recordMemberInContext(const DeclContext *dc, ValueDecl *member) {
@@ -2300,7 +2301,7 @@ namespace {
23002301
return canonicalName;
23012302
}
23022303

2303-
// Special handling when we import using the older Swift name.
2304+
// Special handling when we import using the alternate Swift name.
23042305
//
23052306
// Import using the alternate Swift name. If that fails, or if it's
23062307
// identical to the active Swift name, we won't introduce an alternate
@@ -2309,6 +2310,19 @@ namespace {
23092310
if (!alternateName)
23102311
return ImportedName();
23112312

2313+
// Importing for concurrency is special in that the same declaration
2314+
// is imported both with a completion handler parameter and as 'async',
2315+
// creating two separate declarations.
2316+
if (getVersion().supportsConcurrency()) {
2317+
// If the resulting name isn't special for concurrency, it's not
2318+
// different.
2319+
if (!alternateName.getAsyncInfo())
2320+
return ImportedName();
2321+
2322+
// Otherwise, it's a legitimately different import.
2323+
return alternateName;
2324+
}
2325+
23122326
if (alternateName.getDeclName() == canonicalName.getDeclName() &&
23132327
alternateName.getEffectiveContext().equalsWithoutResolving(
23142328
canonicalName.getEffectiveContext())) {
@@ -2470,6 +2484,13 @@ namespace {
24702484
return;
24712485
}
24722486

2487+
// If this the active and current Swift versions differ based on
2488+
// concurrency, it's not actually a variant.
2489+
if (getVersion().supportsConcurrency() !=
2490+
getActiveSwiftVersion().supportsConcurrency()) {
2491+
return;
2492+
}
2493+
24732494
// TODO: some versions should be deprecated instead of unavailable
24742495

24752496
ASTContext &ctx = decl->getASTContext();
@@ -3837,9 +3858,10 @@ namespace {
38373858

38383859
// FIXME: Poor location info.
38393860
auto nameLoc = Impl.importSourceLoc(decl->getLocation());
3840-
result = createFuncOrAccessor(Impl.SwiftContext, loc, accessorInfo, name,
3841-
nameLoc, bodyParams, resultTy,
3842-
/*throws*/ false, dc, decl);
3861+
result = createFuncOrAccessor(
3862+
Impl.SwiftContext, loc, accessorInfo, name,
3863+
nameLoc, bodyParams, resultTy,
3864+
/*async*/ false, /*throws*/ false, dc, decl);
38433865

38443866
if (!dc->isModuleScopeContext()) {
38453867
if (selfIsInOut)
@@ -4410,6 +4432,16 @@ namespace {
44104432
}
44114433
}
44124434

4435+
// Determine whether the function is throwing and/or async.
4436+
bool throws = importedName.getErrorInfo().hasValue();
4437+
bool async = false;
4438+
auto asyncConvention = importedName.getAsyncInfo();
4439+
if (asyncConvention) {
4440+
async = true;
4441+
if (asyncConvention->isThrowing())
4442+
throws = true;
4443+
}
4444+
44134445
auto resultTy = importedType.getType();
44144446
auto isIUO = importedType.isImplicitlyUnwrapped();
44154447

@@ -4439,8 +4471,7 @@ namespace {
44394471
importedName.getDeclName(),
44404472
/*nameLoc*/SourceLoc(),
44414473
bodyParams, resultTy,
4442-
importedName.getErrorInfo().hasValue(),
4443-
dc, decl);
4474+
async, throws, dc, decl);
44444475

44454476
result->setAccess(getOverridableAccessLevel(dc));
44464477

@@ -4472,6 +4503,11 @@ namespace {
44724503
result->setForeignErrorConvention(*errorConvention);
44734504
}
44744505

4506+
// Record the async convention.
4507+
if (asyncConvention) {
4508+
result->setForeignAsyncConvention(*asyncConvention);
4509+
}
4510+
44754511
// Handle attributes.
44764512
if (decl->hasAttr<clang::IBActionAttr>() &&
44774513
isa<FuncDecl>(result) &&
@@ -4503,6 +4539,7 @@ namespace {
45034539
Impl.addAlternateDecl(result, cast<ValueDecl>(imported));
45044540
}
45054541
}
4542+
45064543
return result;
45074544
}
45084545

@@ -6443,6 +6480,7 @@ ConstructorDecl *SwiftDeclConverter::importConstructor(
64436480
return known->second;
64446481

64456482
// Create the actual constructor.
6483+
assert(!importedName.getAsyncInfo());
64466484
auto result = Impl.createDeclWithClangNode<ConstructorDecl>(
64476485
objcMethod, AccessLevel::Public, importedName.getDeclName(),
64486486
/*NameLoc=*/SourceLoc(), failability, /*FailabilityLoc=*/SourceLoc(),
@@ -7145,6 +7183,11 @@ void SwiftDeclConverter::importMirroredProtocolMembers(
71457183
if (isa<AccessorDecl>(afd))
71467184
return;
71477185

7186+
// Asynch methods are also always imported without async, so don't
7187+
// record them here.
7188+
if (afd->hasAsync())
7189+
return;
7190+
71487191
auto objcMethod =
71497192
dyn_cast_or_null<clang::ObjCMethodDecl>(member->getClangDecl());
71507193
if (!objcMethod)

0 commit comments

Comments
 (0)