Skip to content

Commit b9480f1

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 1695d61 commit b9480f1

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
@@ -8497,6 +8497,16 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
84978497
swiftAttr->getAttribute());
84988498
}
84998499
}
8500+
8501+
// Now that we've collected all @Sendable and @_nonSendable attributes, we
8502+
// can see if we should synthesize a Sendable conformance.
8503+
if (auto nominal = dyn_cast<NominalTypeDecl>(MappedDecl)) {
8504+
auto sendability = nominal->getAttrs().getEffectiveSendableAttr();
8505+
if (isa_and_nonnull<SendableAttr>(sendability)) {
8506+
addSynthesizedProtocolAttrs(*this, nominal, {KnownProtocolKind::Sendable},
8507+
/*isUnchecked=*/true);
8508+
}
8509+
}
85008510
}
85018511

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

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3963,9 +3963,6 @@ NormalProtocolConformance *GetImplicitSendableRequest::evaluate(
39633963
return conformance;
39643964
};
39653965

3966-
if (auto nonSendable = nominal->getAttrs().getAttribute<NonSendableAttr>())
3967-
return formConformance(nonSendable);
3968-
39693966
// A non-protocol type with a global actor is implicitly Sendable.
39703967
if (nominal->getGlobalActorAttr()) {
39713968
// If this is a class, check the superclass. We won't infer Sendable
@@ -3986,6 +3983,12 @@ NormalProtocolConformance *GetImplicitSendableRequest::evaluate(
39863983
return formConformance(nullptr);
39873984
}
39883985

3986+
if (auto attr = nominal->getAttrs().getEffectiveSendableAttr()) {
3987+
assert(!isa<SendableAttr>(attr) &&
3988+
"Conformance should have been added by SynthesizedProtocolAttr!");
3989+
return formConformance(cast<NonSendableAttr>(attr));
3990+
}
3991+
39893992
// Only structs and enums can get implicit Sendable conformances by
39903993
// considering their instance data.
39913994
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
@@ -5,6 +5,21 @@
55

66
#define MAIN_ACTOR __attribute__((__swift_attr__("@MainActor")))
77

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

@@ -166,7 +181,7 @@ __attribute__((__swift_attr__("@MainActor(unsafe)")))
166181
@end
167182

168183
// Do something concurrently, but without escaping.
169-
void doSomethingConcurrently(__attribute__((noescape)) __attribute__((swift_attr("@Sendable"))) void (^block)(void));
184+
void doSomethingConcurrently(__attribute__((noescape)) SENDABLE void (^block)(void));
170185

171186

172187

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

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

0 commit comments

Comments
 (0)