Skip to content

Commit 8c84412

Browse files
committed
[ClangImporter] Make completion handlers Sendable
If a method has an `async` variant, the non-`async` variant will now mark its completion handler parameter `@Sendable`. This shouldn't be a breaking change in Swift 5 code since these declarations are automatically `@_predatesConcurrency`. Also adds: • Support for `@_nonSendable` on parameters, which can be used to override this implicit `@Sendable` • Support for `@Sendable` on block typedefs; it's generally going to be a good idea to mark completion block typedefs `@Sendable`. Fixes rdar://85569247.
1 parent 602fb3f commit 8c84412

13 files changed

+164
-83
lines changed

lib/AST/ASTPrinter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5359,7 +5359,7 @@ class TypePrinter : public TypeVisitor<TypePrinter> {
53595359

53605360
if (!Options.excludeAttrKind(TAK_Sendable) &&
53615361
info.isSendable()) {
5362-
Printer << "@Sendable ";
5362+
Printer.printSimpleAttr("@Sendable") << " ";
53635363
}
53645364

53655365
SmallString<64> buf;

lib/ClangImporter/ImportDecl.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2796,6 +2796,15 @@ namespace {
27962796
SourceLoc(), Name,
27972797
Loc,
27982798
/*genericparams*/nullptr, DC);
2799+
2800+
// If the typedef is marked with @Sendable and not @_nonSendable, make
2801+
// any function type in it Sendable.
2802+
auto sendability = Result->getAttrs().getEffectiveSendableAttr();
2803+
if (isa_and_nonnull<SendableAttr>(sendability))
2804+
SwiftType = applyToFunctionType(SwiftType, [](ASTExtInfo info) {
2805+
return info.withConcurrent();
2806+
});
2807+
27992808
Result->setUnderlyingType(SwiftType);
28002809

28012810
// Make Objective-C's 'id' unavailable.

lib/ClangImporter/ImportName.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2270,6 +2270,17 @@ ImportedName NameImporter::importName(const clang::NamedDecl *decl,
22702270
}
22712271
++ImportNameNumCacheMisses;
22722272
auto res = importNameImpl(decl, version, givenName);
2273+
2274+
// Add information about the async version of the name to the non-async
2275+
// version of the name.
2276+
if (!version.supportsConcurrency()) {
2277+
if (auto importedAsyncName = importName(decl, version.withConcurrency(true),
2278+
givenName)) {
2279+
res.info.hasAsyncAlternateInfo = importedAsyncName.info.hasAsyncInfo;
2280+
res.info.asyncInfo = importedAsyncName.info.asyncInfo;
2281+
}
2282+
}
2283+
22732284
if (!givenName)
22742285
importNameCache[key] = res;
22752286
return res;

lib/ClangImporter/ImportName.h

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,14 @@ class ImportedName {
226226

227227
unsigned hasAsyncInfo : 1;
228228

229+
unsigned hasAsyncAlternateInfo: 1;
230+
229231
Info()
230232
: errorInfo(), selfIndex(), initKind(CtorInitializerKind::Designated),
231233
accessorKind(ImportedAccessorKind::None), hasCustomName(false),
232234
droppedVariadic(false), importAsMember(false), hasSelfIndex(false),
233-
hasErrorInfo(false), hasAsyncInfo(false) {}
235+
hasErrorInfo(false), hasAsyncInfo(false),
236+
hasAsyncAlternateInfo(false) {}
234237
} info;
235238

236239
public:
@@ -267,8 +270,27 @@ class ImportedName {
267270
/// For names that map Objective-C methods with completion handlers into
268271
/// async Swift methods, describes how the mapping is performed.
269272
Optional<ForeignAsyncConvention::Info> getAsyncInfo() const {
270-
if (info.hasAsyncInfo)
273+
if (info.hasAsyncInfo) {
274+
assert(!info.hasAsyncAlternateInfo
275+
&& "both regular and alternate async info?");
276+
return info.asyncInfo;
277+
}
278+
return None;
279+
}
280+
281+
/// For names with a variant that maps Objective-C methods with completion
282+
/// handlers into async Swift methods, describes how the mapping is performed.
283+
///
284+
/// That is, if the method imports as both an async method and a completion
285+
/// handler method, this value is set on the completion handler method's name
286+
/// and gives you the contents of \c getAsyncInfo() on the async method's
287+
/// name. It is not set on the async method's name, and it is not set if a
288+
/// non-async method doesn't have an async equivalent.
289+
Optional<ForeignAsyncConvention::Info> getAsyncAlternateInfo() const {
290+
if (info.hasAsyncAlternateInfo) {
291+
assert(!info.hasAsyncInfo && "both regular and alternate async info?");
271292
return info.asyncInfo;
293+
}
272294
return None;
273295
}
274296

lib/ClangImporter/ImportType.cpp

Lines changed: 51 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1725,63 +1725,58 @@ ImportedType ClangImporter::Implementation::importPropertyType(
17251725
Bridgeability::Full, optionality);
17261726
}
17271727

1728-
/// Apply an attribute to a function type.
1729-
static Type applyToFunctionType(
1730-
Type type, llvm::function_ref<ASTExtInfo(ASTExtInfo)> transform) {
1731-
// Recurse into optional types.
1732-
if (Type objectType = type->getOptionalObjectType()) {
1733-
return OptionalType::get(applyToFunctionType(objectType, transform));
1734-
}
1735-
1736-
// Apply @noescape to function types.
1737-
if (auto funcType = type->getAs<FunctionType>()) {
1738-
return FunctionType::get(funcType->getParams(), funcType->getResult(),
1739-
transform(funcType->getExtInfo()));
1740-
}
1741-
1742-
return type;
1743-
}
1744-
17451728
Type ClangImporter::Implementation::applyParamAttributes(
1746-
const clang::ParmVarDecl *param, Type type) {
1747-
if (!param->hasAttrs())
1748-
return type;
1729+
const clang::ParmVarDecl *param, Type type, bool sendableByDefault) {
1730+
bool sendableRequested = sendableByDefault;
1731+
bool sendableDisqualified = false;
1732+
1733+
if (param->hasAttrs()) {
1734+
for (auto attr : param->getAttrs()) {
1735+
// Map __attribute__((noescape)) to @noescape.
1736+
if (isa<clang::NoEscapeAttr>(attr)) {
1737+
type = applyToFunctionType(type, [](ASTExtInfo extInfo) {
1738+
return extInfo.withNoEscape();
1739+
});
17491740

1750-
for (auto attr : param->getAttrs()) {
1751-
// Map __attribute__((noescape)) to @noescape.
1752-
if (isa<clang::NoEscapeAttr>(attr)) {
1753-
type = applyToFunctionType(type, [](ASTExtInfo extInfo) {
1754-
return extInfo.withNoEscape();
1755-
});
1741+
continue;
1742+
}
17561743

1757-
continue;
1758-
}
1744+
auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr);
1745+
if (!swiftAttr)
1746+
continue;
17591747

1760-
auto swiftAttr = dyn_cast<clang::SwiftAttrAttr>(attr);
1761-
if (!swiftAttr)
1762-
continue;
1748+
// Map the main-actor attribute.
1749+
if (isMainActorAttr(swiftAttr)) {
1750+
if (Type mainActor = SwiftContext.getMainActorType()) {
1751+
type = applyToFunctionType(type, [&](ASTExtInfo extInfo) {
1752+
return extInfo.withGlobalActor(mainActor);
1753+
});
1754+
sendableDisqualified = true;
1755+
}
17631756

1764-
// Map the main-actor attribute.
1765-
if (isMainActorAttr(swiftAttr)) {
1766-
if (Type mainActor = SwiftContext.getMainActorType()) {
1767-
type = applyToFunctionType(type, [&](ASTExtInfo extInfo) {
1768-
return extInfo.withGlobalActor(mainActor);
1769-
});
1757+
continue;
17701758
}
17711759

1772-
continue;
1773-
}
1774-
1775-
// Map @Sendable.
1776-
if (swiftAttr->getAttribute() == "@Sendable") {
1777-
type = applyToFunctionType(type, [](ASTExtInfo extInfo) {
1778-
return extInfo.withConcurrent();
1779-
});
1760+
// Map @Sendable.
1761+
if (swiftAttr->getAttribute() == "@Sendable") {
1762+
sendableRequested = true;
1763+
continue;
1764+
}
17801765

1781-
continue;
1766+
// Map @_nonSendable.
1767+
if (swiftAttr->getAttribute() == "@_nonSendable") {
1768+
sendableDisqualified = true;
1769+
continue;
1770+
}
17821771
}
17831772
}
17841773

1774+
if (!sendableDisqualified && sendableRequested) {
1775+
type = applyToFunctionType(type, [](ASTExtInfo extInfo) {
1776+
return extInfo.withConcurrent();
1777+
});
1778+
}
1779+
17851780
return type;
17861781
}
17871782

@@ -1986,7 +1981,7 @@ ParameterList *ClangImporter::Implementation::importFunctionParameterList(
19861981

19871982
// Apply attributes to the type.
19881983
swiftParamTy = applyParamAttributes(
1989-
param, swiftParamTy);
1984+
param, swiftParamTy, /*sendableByDefault=*/false);
19901985

19911986
// Figure out the name for this parameter.
19921987
Identifier bodyName = importFullName(param, CurrentVersion)
@@ -2372,6 +2367,10 @@ ImportedType ClangImporter::Implementation::importMethodParamsAndReturnType(
23722367
Optional<ForeignErrorConvention::Info> errorInfo =
23732368
importedName.getErrorInfo();
23742369
auto asyncInfo = importedName.getAsyncInfo();
2370+
bool isAsync = asyncInfo.hasValue();
2371+
if (!isAsync)
2372+
asyncInfo = importedName.getAsyncAlternateInfo();
2373+
23752374
OptionalTypeKind OptionalityOfReturn;
23762375
if (clangDecl->hasAttr<clang::ReturnsNonNullAttr>()) {
23772376
OptionalityOfReturn = OTK_None;
@@ -2518,7 +2517,7 @@ ImportedType ClangImporter::Implementation::importMethodParamsAndReturnType(
25182517
// Figure out if this is a completion handler parameter whose error
25192518
// parameter is used to indicate throwing.
25202519
Optional<unsigned> completionHandlerErrorParamIndex;
2521-
if (paramIsCompletionHandler) {
2520+
if (isAsync && paramIsCompletionHandler) {
25222521
completionHandlerErrorParamIndex =
25232522
asyncInfo->completionHandlerErrorParamIndex();
25242523
}
@@ -2563,7 +2562,7 @@ ImportedType ClangImporter::Implementation::importMethodParamsAndReturnType(
25632562

25642563
// If this is a completion handler, figure out it's effect on the result
25652564
// type but don't build it into the parameter type.
2566-
if (paramIsCompletionHandler) {
2565+
if (isAsync && paramIsCompletionHandler) {
25672566
if (Type replacedSwiftResultTy =
25682567
decomposeCompletionHandlerType(swiftParamTy, *asyncInfo)) {
25692568
swiftResultTy = replacedSwiftResultTy;
@@ -2582,7 +2581,8 @@ ImportedType ClangImporter::Implementation::importMethodParamsAndReturnType(
25822581
}
25832582

25842583
// Apply Clang attributes to the parameter type.
2585-
swiftParamTy = applyParamAttributes(param, swiftParamTy);
2584+
swiftParamTy = applyParamAttributes(param, swiftParamTy,
2585+
/*sendableByDefault=*/paramIsCompletionHandler);
25862586

25872587
// Figure out the name for this parameter.
25882588
Identifier bodyName = importFullName(param, CurrentVersion)
@@ -2657,7 +2657,7 @@ ImportedType ClangImporter::Implementation::importMethodParamsAndReturnType(
26572657
swiftResultTy = SwiftContext.getNeverType();
26582658
}
26592659

2660-
if (asyncInfo) {
2660+
if (isAsync) {
26612661
asyncConvention = ForeignAsyncConvention(
26622662
completionHandlerType, asyncInfo->completionHandlerParamIndex(),
26632663
asyncInfo->completionHandlerErrorParamIndex(),

lib/ClangImporter/ImporterImpl.h

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -942,7 +942,8 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
942942
void importAttributes(const clang::NamedDecl *ClangDecl, Decl *MappedDecl,
943943
const clang::ObjCContainerDecl *NewContext = nullptr);
944944

945-
Type applyParamAttributes(const clang::ParmVarDecl *param, Type type);
945+
Type applyParamAttributes(const clang::ParmVarDecl *param, Type type,
946+
bool sendableByDefault);
946947

947948
/// If we already imported a given decl, return the corresponding Swift decl.
948949
/// Otherwise, return nullptr.
@@ -1754,6 +1755,25 @@ class SwiftNameLookupExtension : public clang::ModuleFileExtension {
17541755
/// actor.
17551756
bool isMainActorAttr(const clang::SwiftAttrAttr *swiftAttr);
17561757

1758+
/// Apply an attribute to a function type.
1759+
static inline Type applyToFunctionType(
1760+
Type type, llvm::function_ref<ASTExtInfo(ASTExtInfo)> transform) {
1761+
// Recurse into optional types.
1762+
if (Type objectType = type->getOptionalObjectType()) {
1763+
return OptionalType::get(applyToFunctionType(objectType, transform));
1764+
}
1765+
1766+
// Apply transform to function types.
1767+
if (auto funcType = type->getAs<FunctionType>()) {
1768+
auto newExtInfo = transform(funcType->getExtInfo());
1769+
if (!newExtInfo.isEqualTo(funcType->getExtInfo(), /*useClangTypes=*/true))
1770+
return FunctionType::get(funcType->getParams(), funcType->getResult(),
1771+
newExtInfo);
1772+
}
1773+
1774+
return type;
1775+
}
1776+
17571777
}
17581778
}
17591779

test/IDE/print_clang_objc_async.swift

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,47 +10,52 @@ import _Concurrency
1010
// CHECK-LABEL: class SlowServer : NSObject, ServiceProvider {
1111

1212
// CHECK: @available(*, renamed: "doSomethingSlow(_:)")
13-
// CHECK-NEXT: func doSomethingSlow(_ operation: String, completionHandler handler: @escaping (Int) -> Void)
13+
// CHECK-NEXT: func doSomethingSlow(_ operation: String, completionHandler handler: @escaping @Sendable (Int) -> Void)
1414
// CHECK-NEXT: @discardableResult
1515
// CHECK-NEXT: func doSomethingSlow(_ operation: String) async -> Int
1616

1717
// CHECK: @available(*, renamed: "doSomethingDangerous(_:)")
18-
// CHECK-NEXT: func doSomethingDangerous(_ operation: String, completionHandler handler: ((String?, Error?) -> Void)? = nil)
18+
// CHECK-NEXT: func doSomethingDangerous(_ operation: String, completionHandler handler: (@Sendable (String?, Error?) -> Void)? = nil)
1919
// CHECK-NEXT: @discardableResult
2020
// CHECK-NEXT: func doSomethingDangerous(_ operation: String) async throws -> String
2121

22+
// CHECK: @available(*, renamed: "doSomethingReckless(_:)")
23+
// CHECK-NEXT: func doSomethingReckless(_ operation: String, completionHandler handler: ((String?, Error?) -> Void)? = nil)
24+
// CHECK-NEXT: @discardableResult
25+
// CHECK-NEXT: func doSomethingReckless(_ operation: String) async throws -> String
26+
2227
// CHECK: @available(*, renamed: "checkAvailability()")
23-
// CHECK-NEXT: func checkAvailability(completionHandler: @escaping (Bool) -> Void)
28+
// CHECK-NEXT: func checkAvailability(completionHandler: @escaping @Sendable (Bool) -> Void)
2429
// CHECK-NEXT: @discardableResult
2530
// CHECK-NEXT: func checkAvailability() async -> Bool
2631

2732
// CHECK: @available(*, renamed: "anotherExample()")
28-
// CHECK-NEXT: func anotherExample(completionBlock block: @escaping (String) -> Void)
33+
// CHECK-NEXT: func anotherExample(completionBlock block: @escaping @Sendable (String) -> Void)
2934
// CHECK-NEXT: @discardableResult
3035
// CHECK-NEXT: func anotherExample() async -> String
3136

3237
// CHECK: @available(*, renamed: "finalExample()")
33-
// CHECK-NEXT: func finalExampleWithReply(to block: @escaping (String) -> Void)
38+
// CHECK-NEXT: func finalExampleWithReply(to block: @escaping @Sendable (String) -> Void)
3439
// CHECK-NEXT: @discardableResult
3540
// CHECK-NEXT: func finalExample() async -> String
3641

3742
// CHECK: @available(*, renamed: "replyingOperation(_:)")
38-
// CHECK-NEXT: func replyingOperation(_ operation: String, replyTo block: @escaping (String) -> Void)
43+
// CHECK-NEXT: func replyingOperation(_ operation: String, replyTo block: @escaping @Sendable (String) -> Void)
3944
// CHECK-NEXT: @discardableResult
4045
// CHECK-NEXT: func replyingOperation(_ operation: String) async -> String
4146

4247
// CHECK: @available(*, renamed: "findAnswer()")
43-
// CHECK-NEXT: func findAnswer(completionHandler handler: @escaping (String?, Error?) -> Void)
48+
// CHECK-NEXT: func findAnswer(completionHandler handler: @escaping @Sendable (String?, Error?) -> Void)
4449
// CHECK-NEXT: @discardableResult
4550
// CHECK-NEXT: func findAnswer() async throws -> String
4651

4752
// CHECK: @available(*, renamed: "findAnswerFailingly()")
48-
// CHECK-NEXT: func findAnswerFailingly(completionHandler handler: @escaping (String?, Error?) -> Void) throws
53+
// CHECK-NEXT: func findAnswerFailingly(completionHandler handler: @escaping @Sendable (String?, Error?) -> Void) throws
4954
// CHECK-NEXT: @discardableResult
5055
// CHECK-NEXT: func findAnswerFailingly() async throws -> String
5156

5257
// CHECK: @available(*, renamed: "findQAndA()")
53-
// CHECK-NEXT: func findQAndA(completionHandler handler: @escaping (String?, String?, Error?) -> Void)
58+
// CHECK-NEXT: func findQAndA(completionHandler handler: @escaping @Sendable (String?, String?, Error?) -> Void)
5459
// CHECK-NEXT: @discardableResult
5560
// CHECK-NEXT: func findQAndA() async throws -> (String?, String)
5661

@@ -59,12 +64,22 @@ import _Concurrency
5964
// CHECK-NEXT: @discardableResult
6065
// CHECK-NEXT: func findQuestionableAnswers() async throws -> (String, String?)
6166

67+
// CHECK: @available(*, renamed: "findAnswerableQuestions()")
68+
// CHECK-NEXT: func findAnswerableQuestions(completionHandler handler: @escaping @Sendable (String?, String?, Error?) -> Void)
69+
// CHECK-NEXT: @discardableResult
70+
// CHECK-NEXT: func findAnswerableQuestions() async throws -> (String, String?)
71+
72+
// CHECK: @available(*, renamed: "findUnanswerableQuestions()")
73+
// CHECK-NEXT: func findUnanswerableQuestions(completionHandler handler: @escaping NonsendableCompletionHandler)
74+
// CHECK-NEXT: @discardableResult
75+
// CHECK-NEXT: func findUnanswerableQuestions() async throws -> (String, String?)
76+
6277
// CHECK: @available(*, renamed: "doSomethingFun(_:)")
63-
// CHECK-NEXT: func doSomethingFun(_ operation: String, then completionHandler: @escaping () -> Void)
78+
// CHECK-NEXT: func doSomethingFun(_ operation: String, then completionHandler: @escaping @Sendable () -> Void)
6479
// CHECK-NEXT: func doSomethingFun(_ operation: String) async
6580

6681
// CHECK: @available(*, renamed: "doSomethingConflicted(_:)")
67-
// CHECK-NEXT: func doSomethingConflicted(_ operation: String, completionHandler handler: @escaping (Int) -> Void)
82+
// CHECK-NEXT: func doSomethingConflicted(_ operation: String, completionHandler handler: @escaping @Sendable (Int) -> Void)
6883
// CHECK-NEXT: @discardableResult
6984
// CHECK-NEXT: func doSomethingConflicted(_ operation: String) async -> Int
7085
// CHECK-NEXT: @discardableResult
@@ -79,7 +94,7 @@ import _Concurrency
7994
// CHECK-NEXT: func runOnMainThread() async -> String
8095

8196
// CHECK: @available(*, renamed: "asyncImportSame(_:)")
82-
// CHECK-NEXT: func asyncImportSame(_ operation: String, completionHandler handler: @escaping (Int) -> Void)
97+
// CHECK-NEXT: func asyncImportSame(_ operation: String, completionHandler handler: @escaping @Sendable (Int) -> Void)
8398
// CHECK-NEXT: @discardableResult
8499
// CHECK-NEXT: func asyncImportSame(_ operation: String) async -> Int
85100
// CHECK-NEXT: func asyncImportSame(_ operation: String, replyTo handler: @escaping (Int) -> Void)

0 commit comments

Comments
 (0)