Skip to content

Commit 7549278

Browse files
committed
Allow imported types to add a Sendable conformance
...by using `__attribute__((swift_attr("@sendable")))`. `@_nonSendable` will "beat" `@Sendable`, while `@_nonSendable(_assumed)` will not. This commit also checks if `SwiftAttr` supports `#pragma clang attribute` and, if it does, defines `__SWIFT_ATTR_SUPPORTS_SENDABLE_DECLS` in imported headers so they know they can apply these attributes in an auditing style.
1 parent de768e8 commit 7549278

File tree

9 files changed

+124
-7
lines changed

9 files changed

+124
-7
lines changed

include/swift/AST/Attr.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ CONTEXTUAL_SIMPLE_DECL_ATTR(async, Async,
605605
106)
606606

607607
SIMPLE_DECL_ATTR(Sendable, Sendable,
608-
OnFunc | OnConstructor | OnAccessor |
608+
OnFunc | OnConstructor | OnAccessor | OnAnyClangDecl |
609609
ABIBreakingToAdd | ABIBreakingToRemove |
610610
APIBreakingToAdd | APIBreakingToRemove,
611611
107)

include/swift/AST/Attr.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,9 @@ class DeclAttribute : public AttributeBase {
289289

290290
/// Whether this attribute is only valid when distributed is enabled.
291291
DistributedOnly = 1ull << (unsigned(DeclKindIndex::Last_Decl) + 17),
292+
293+
/// Whether this attribute is valid on additional decls in ClangImporter.
294+
OnAnyClangDecl = 1ull << (unsigned(DeclKindIndex::Last_Decl) + 18),
292295
};
293296

294297
LLVM_READNONE
@@ -2224,6 +2227,10 @@ class DeclAttributes {
22242227
return nullptr;
22252228
}
22262229

2230+
/// Returns the "winning" \c NonSendableAttr or \c SendableAttr in this
2231+
/// attribute list, or \c nullptr if there are none.
2232+
const DeclAttribute *getEffectiveSendableAttr() const;
2233+
22272234
private:
22282235
/// Predicate used to filter MatchingAttributeRange.
22292236
template <typename ATTR, bool AllowInvalid> struct ToAttributeKind {

lib/AST/Attr.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ DeclAttrKind DeclAttribute::getAttrKindFromString(StringRef Str) {
131131

132132
/// Returns true if this attribute can appear on the specified decl.
133133
bool DeclAttribute::canAttributeAppearOnDecl(DeclAttrKind DK, const Decl *D) {
134+
if ((getOptions(DK) & OnAnyClangDecl) && D->hasClangNode())
135+
return true;
134136
return canAttributeAppearOnDeclKind(DK, D->getKind());
135137
}
136138

@@ -699,6 +701,13 @@ void DeclAttributes::print(ASTPrinter &Printer, const PrintOptions &Options,
699701
if (Options.excludeAttrKind(DA->getKind()))
700702
continue;
701703

704+
// If this attribute is only allowed because this is a Clang decl, don't
705+
// print it.
706+
if (D && D->hasClangNode()
707+
&& !DeclAttribute::canAttributeAppearOnDeclKind(
708+
DA->getKind(), D->getKind()))
709+
continue;
710+
702711
// Be careful not to coalesce `@available(swift 5)` with other short
703712
// `available' attributes.
704713
if (auto *availableAttr = dyn_cast<AvailableAttr>(DA)) {
@@ -2102,6 +2111,23 @@ TypeSequenceAttr *TypeSequenceAttr::create(ASTContext &Ctx, SourceLoc atLoc,
21022111
return new (mem) TypeSequenceAttr(atLoc, range);
21032112
}
21042113

2114+
const DeclAttribute *
2115+
DeclAttributes::getEffectiveSendableAttr() const {
2116+
const NonSendableAttr *assumedAttr = nullptr;
2117+
2118+
for (auto attr : getAttributes<NonSendableAttr>()) {
2119+
if (attr->Specificity == NonSendableKind::Specific)
2120+
return attr;
2121+
if (!assumedAttr)
2122+
assumedAttr = attr;
2123+
}
2124+
2125+
if (auto sendableAttr = getAttribute<SendableAttr>())
2126+
return sendableAttr;
2127+
2128+
return assumedAttr;
2129+
}
2130+
21052131
void swift::simple_display(llvm::raw_ostream &out, const DeclAttribute *attr) {
21062132
if (attr)
21072133
attr->print(out);

lib/ClangImporter/ClangImporter.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,14 @@ getGlibcModuleMapPath(SearchPathOptions& Opts, llvm::Triple triple,
467467
return None;
468468
}
469469

470+
static bool clangSupportsPragmaAttributeWithSwiftAttr() {
471+
clang::AttributeCommonInfo swiftAttrInfo(clang::SourceRange(),
472+
clang::AttributeCommonInfo::AT_SwiftAttr,
473+
clang::AttributeCommonInfo::AS_GNU);
474+
auto swiftAttrParsedInfo = clang::ParsedAttrInfo::get(swiftAttrInfo);
475+
return swiftAttrParsedInfo.IsSupportedByPragmaAttribute;
476+
}
477+
470478
void
471479
importer::getNormalInvocationArguments(
472480
std::vector<std::string> &invocationArgStrs,
@@ -610,6 +618,12 @@ importer::getNormalInvocationArguments(
610618
"-DSWIFT_CLASS_EXTRA=",
611619
});
612620

621+
// Indicate that using '__attribute__((swift_attr))' with '@Sendable' and
622+
// '@_nonSendable' on Clang declarations is fully supported, including the
623+
// 'attribute push' pragma.
624+
if (clangSupportsPragmaAttributeWithSwiftAttr())
625+
invocationArgStrs.push_back( "-D__SWIFT_ATTR_SUPPORTS_SENDABLE_DECLS=1");
626+
613627
// Get the version of this compiler and pass it to C/Objective-C
614628
// declarations.
615629
auto V = version::Version::getCurrentCompilerVersion();

lib/ClangImporter/ImportDecl.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8495,6 +8495,16 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
84958495
swiftAttr->getAttribute());
84968496
}
84978497
}
8498+
8499+
// Now that we've collected all @Sendable and @_nonSendable attributes, we
8500+
// can see if we should synthesize a Sendable conformance.
8501+
if (auto nominal = dyn_cast<NominalTypeDecl>(MappedDecl)) {
8502+
auto sendability = nominal->getAttrs().getEffectiveSendableAttr();
8503+
if (isa_and_nonnull<SendableAttr>(sendability)) {
8504+
addSynthesizedProtocolAttrs(*this, nominal, {KnownProtocolKind::Sendable},
8505+
/*isUnchecked=*/true);
8506+
}
8507+
}
84988508
}
84998509

85008510
static bool isUsingMacroName(clang::SourceManager &SM,

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4035,9 +4035,6 @@ NormalProtocolConformance *GetImplicitSendableRequest::evaluate(
40354035
return conformance;
40364036
};
40374037

4038-
if (auto nonSendable = nominal->getAttrs().getAttribute<NonSendableAttr>())
4039-
return formConformance(nonSendable);
4040-
40414038
// A non-protocol type with a global actor is implicitly Sendable.
40424039
if (nominal->getGlobalActorAttr()) {
40434040
// If this is a class, check the superclass. We won't infer Sendable
@@ -4058,6 +4055,12 @@ NormalProtocolConformance *GetImplicitSendableRequest::evaluate(
40584055
return formConformance(nullptr);
40594056
}
40604057

4058+
if (auto attr = nominal->getAttrs().getEffectiveSendableAttr()) {
4059+
assert(!isa<SendableAttr>(attr) &&
4060+
"Conformance should have been added by SynthesizedProtocolAttr!");
4061+
return formConformance(cast<NonSendableAttr>(attr));
4062+
}
4063+
40614064
// Only structs and enums can get implicit Sendable conformances by
40624065
// considering their instance data.
40634066
if (!isa<StructDecl>(nominal) && !isa<EnumDecl>(nominal))

test/ClangImporter/objc_async.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -I %S/Inputs/custom-modules -disable-availability-checking %s -verify
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -I %S/Inputs/custom-modules -disable-availability-checking %s -verify -warn-concurrency
22

33
// REQUIRES: objc_interop
44
// REQUIRES: concurrency
@@ -111,6 +111,18 @@ func testSendableInAsync() async {
111111
print(x)
112112
}
113113

114+
func testSendableClasses(sendable: SendableClass, nonSendable: NonSendableClass) async {
115+
func takesSendable<T: Sendable>(_: T) {}
116+
117+
takesSendable(sendable) // no-error
118+
takesSendable(nonSendable) // expected-FIXME-warning{{something about missing conformance}}
119+
120+
doSomethingConcurrently {
121+
print(sendable) // no-error
122+
print(nonSendable) // expected-warning{{cannot use parameter 'nonSendable' with a non-sendable type 'NonSendableClass' from concurrently-executed code}}
123+
}
124+
}
125+
114126
// Check import of attributes
115127
func globalAsync() async { }
116128

test/IDE/print_objc_concurrency_interface.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// RUN: %empty-directory(%t)
22

3-
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -print-module -print-interface -source-filename %s -module-to-print=ObjCConcurrency -function-definitions=false | %FileCheck %s
3+
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -print-module -print-interface -source-filename %s -module-to-print=ObjCConcurrency -function-definitions=false > %t/ObjCConcurrency.printed.txt
4+
// RUN: %FileCheck -input-file %t/ObjCConcurrency.printed.txt %s
5+
// RUN: %FileCheck -check-prefix NEGATIVE -input-file %t/ObjCConcurrency.printed.txt %s
6+
47

58
// REQUIRES: objc_interop
69
// REQUIRES: concurrency
@@ -12,3 +15,18 @@ import _Concurrency
1215
// CHECK-NOT: @available
1316
// CHECK: func doSomethingSlow(_ operation: String, completionHandler handler: @escaping (Int) -> Void)
1417
// CHECK: func doSomethingSlow(_ operation: String) async -> Int
18+
19+
// NEGATIVE-NOT: @Sendable{{.+}}class
20+
// NEGATIVE-NOT: @_nonSendable{{.+}}class
21+
22+
// CHECK-LABEL: class SendableClass :
23+
// CHECK-SAME: @unchecked Sendable
24+
25+
// CHECK-LABEL: class NonSendableClass
26+
27+
// CHECK-LABEL: class AuditedSendable :
28+
// CHECK-SAME: @unchecked Sendable
29+
30+
// CHECK-LABEL: class AuditedNonSendable
31+
32+
// CHECK-LABEL: class AuditedBoth

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@
66
#define MAIN_ACTOR __attribute__((__swift_attr__("@MainActor")))
77
#define MAIN_ACTOR_UNSAFE __attribute__((__swift_attr__("@_unsafeMainActor")))
88

9+
#ifdef __SWIFT_ATTR_SUPPORTS_SENDABLE_DECLS
10+
#define SENDABLE __attribute__((__swift_attr__("@Sendable")))
11+
#define NONSENDABLE __attribute__((__swift_attr__("@_nonSendable")))
12+
#define ASSUME_NONSENDABLE_BEGIN _Pragma("clang attribute ASSUME_NONSENDABLE.push (__attribute__((swift_attr(\"@_nonSendable(_assumed)\"))), apply_to = any(objc_interface, record, enum))")
13+
#define ASSUME_NONSENDABLE_END _Pragma("clang attribute ASSUME_NONSENDABLE.pop")
14+
#else
15+
// If we take this #else, we should see minor failures of some subtests,
16+
// but not systematic failures of everything that uses this header.
17+
#define SENDABLE
18+
#define NONSENDABLE
19+
#define ASSUME_NONSENDABLE_BEGIN
20+
#define ASSUME_NONSENDABLE_END
21+
#endif
22+
23+
924
#define NS_EXTENSIBLE_STRING_ENUM __attribute__((swift_wrapper(struct)));
1025
typedef NSString *Flavor NS_EXTENSIBLE_STRING_ENUM;
1126

@@ -167,7 +182,7 @@ __attribute__((__swift_attr__("@MainActor(unsafe)")))
167182
@end
168183

169184
// Do something concurrently, but without escaping.
170-
void doSomethingConcurrently(__attribute__((noescape)) __attribute__((swift_attr("@Sendable"))) void (^block)(void));
185+
void doSomethingConcurrently(__attribute__((noescape)) SENDABLE void (^block)(void));
171186

172187

173188

@@ -186,4 +201,16 @@ MAIN_ACTOR MAIN_ACTOR __attribute__((__swift_attr__("@MainActor(unsafe)"))) @pro
186201
- (void)instanceMethodWithCompletionHandler:(void (^)(void))completionHandler __attribute__((swift_async_name("instanceAsync()")));
187202
@end
188203

204+
SENDABLE @interface SendableClass : NSObject @end
205+
206+
NONSENDABLE @interface NonSendableClass : NSObject @end
207+
208+
ASSUME_NONSENDABLE_BEGIN
209+
210+
SENDABLE @interface AuditedSendable : NSObject @end
211+
@interface AuditedNonSendable : NSObject @end
212+
NONSENDABLE SENDABLE @interface AuditedBoth : NSObject @end
213+
214+
ASSUME_NONSENDABLE_END
215+
189216
#pragma clang assume_nonnull end

0 commit comments

Comments
 (0)