Skip to content

Commit 65599ce

Browse files
committed
[AST/Sema] Allow Sendable suppression on Objective-C class declarations
Expressed as `__swift_attr__("~Sendable")` this acts like `: ~Sendable` on Swift type declarations and supersedes `@_nonSendable(_assumed)`. Resolves: rdar://140928937
1 parent 2c1329e commit 65599ce

File tree

6 files changed

+83
-6
lines changed

6 files changed

+83
-6
lines changed

lib/AST/Attr.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3306,6 +3306,13 @@ DeclAttributes::getEffectiveSendableAttr() const {
33063306
if (auto sendableAttr = getAttribute<SendableAttr>())
33073307
return sendableAttr;
33083308

3309+
// ~Sendable on declarations imported from Objective-C.
3310+
for (auto *attr : getAttributes<SynthesizedProtocolAttr>()) {
3311+
if (attr->getProtocol()->isSpecificProtocol(KnownProtocolKind::Sendable) &&
3312+
attr->isSuppressed())
3313+
return nullptr;
3314+
}
3315+
33093316
return assumedAttr;
33103317
}
33113318

lib/AST/NameLookup.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4168,7 +4168,7 @@ swift::getDirectlyInheritedNominalTypeDecls(
41684168
if (attr->isUnchecked())
41694169
attributes.uncheckedLoc = loc;
41704170
result.push_back({attr->getProtocol(), loc, /*inheritedTypeRepr=*/nullptr,
4171-
attributes, /*isSuppressed=*/false});
4171+
attributes, attr->isSuppressed()});
41724172
}
41734173

41744174
// Else we have access to this information on the where clause.

lib/AST/ProtocolConformance.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,8 @@ void NominalTypeDecl::prepareConformanceTable() const {
12481248

12491249
// Add protocols for any synthesized protocol attributes.
12501250
for (auto attr : getAttrs().getAttributes<SynthesizedProtocolAttr>()) {
1251+
if (attr->isSuppressed())
1252+
continue;
12511253
addSynthesized(attr->getProtocol());
12521254
}
12531255

lib/ClangImporter/ImportDecl.cpp

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6641,7 +6641,7 @@ static bool conformsToProtocolInOriginalModule(NominalTypeDecl *nominal,
66416641
for (auto attr : nominal->getAttrs().getAttributes<SynthesizedProtocolAttr>()) {
66426642
auto *otherProto = attr->getProtocol();
66436643
if (otherProto == proto || otherProto->inheritsFrom(proto))
6644-
return true;
6644+
return !attr->isSuppressed();
66456645
}
66466646

66476647
// Only consider extensions from the original module...or from an overlay
@@ -8949,6 +8949,7 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
89498949
std::optional<const clang::SwiftAttrAttr *> seenMainActorAttr;
89508950
const clang::SwiftAttrAttr *seenMutabilityAttr = nullptr;
89518951
llvm::SmallSet<ProtocolDecl *, 4> conformancesSeen;
8952+
const clang::SwiftAttrAttr *seenSendableSuppressionAttr = nullptr;
89528953

89538954
auto importAttrsFromDecl = [&](const clang::NamedDecl *ClangDecl) {
89548955
//
@@ -9041,6 +9042,18 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
90419042
nominal->registerProtocolConformance(conformance, /*synthesized=*/true);
90429043
}
90439044

9045+
if (swiftAttr->getAttribute() == "~Sendable") {
9046+
auto *nominal = dyn_cast<NominalTypeDecl>(MappedDecl);
9047+
if (!nominal)
9048+
continue;
9049+
9050+
seenSendableSuppressionAttr = swiftAttr;
9051+
addSynthesizedProtocolAttrs(nominal, {KnownProtocolKind::Sendable},
9052+
/*isUnchecked=*/false,
9053+
/*isSuppressed=*/true);
9054+
continue;
9055+
}
9056+
90449057
if (swiftAttr->getAttribute() == "sending") {
90459058
// Swallow this if the feature is not enabled.
90469059
if (!SwiftContext.LangOpts.hasFeature(Feature::SendingArgsAndResults))
@@ -9108,10 +9121,11 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
91089121
MappedDecl->getAttrs().removeAttribute(attr);
91099122

91109123
// Some types have an implicit '@Sendable' attribute.
9111-
if (ClangDecl->hasAttr<clang::SwiftNewTypeAttr>() ||
9112-
ClangDecl->hasAttr<clang::EnumExtensibilityAttr>() ||
9113-
ClangDecl->hasAttr<clang::FlagEnumAttr>() ||
9114-
ClangDecl->hasAttr<clang::NSErrorDomainAttr>())
9124+
if ((ClangDecl->hasAttr<clang::SwiftNewTypeAttr>() ||
9125+
ClangDecl->hasAttr<clang::EnumExtensibilityAttr>() ||
9126+
ClangDecl->hasAttr<clang::FlagEnumAttr>() ||
9127+
ClangDecl->hasAttr<clang::NSErrorDomainAttr>()) &&
9128+
!seenSendableSuppressionAttr)
91159129
MappedDecl->addAttribute(new (SwiftContext)
91169130
SendableAttr(/*isImplicit=*/true));
91179131

lib/Sema/TypeCheckRequestFunctions.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,13 @@ bool SuppressesConformanceRequest::evaluate(Evaluator &evaluator,
172172
if (other == kp)
173173
return true;
174174
}
175+
176+
for (auto *attr :
177+
nominal->getAttrs().getAttributes<SynthesizedProtocolAttr>()) {
178+
if (attr->getProtocol()->isSpecificProtocol(kp) && attr->isSuppressed())
179+
return true;
180+
}
181+
175182
return false;
176183
}
177184

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// RUN: %empty-directory(%t/src)
2+
// RUN: %empty-directory(%t/sdk)
3+
// RUN: split-file %s %t/src
4+
5+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck %t/src/main.swift \
6+
// RUN: -import-objc-header %t/src/Test.h \
7+
// RUN: -swift-version 6 \
8+
// RUN: -enable-experimental-feature TildeSendable \
9+
// RUN: -module-name main -I %t -verify -verify-ignore-unrelated
10+
11+
// REQUIRES: objc_interop
12+
// REQUIRES: swift_feature_TildeSendable
13+
14+
//--- Test.h
15+
#define SWIFT_SENDABLE __attribute__((__swift_attr__("@Sendable")))
16+
#define SWIFT_NONSENDABLE_ASSUMED __attribute__((__swift_attr__("@_nonSendable(_assumed)")))
17+
#define SWIFT_SUPPRESS_SENDABLE __attribute__((__swift_attr__("~Sendable")))
18+
19+
@import Foundation;
20+
21+
// Test that `~Sendable` superseeds `@_nonSendable(_assumed)` on classes.
22+
23+
SWIFT_NONSENDABLE_ASSUMED
24+
SWIFT_SUPPRESS_SENDABLE
25+
@interface Parent : NSObject
26+
@end
27+
28+
// Test that `Sendable` superseeds `@_nonSendable(_assumed)` and `~Sendable` from the parent.
29+
30+
SWIFT_NONSENDABLE_ASSUMED
31+
SWIFT_SENDABLE
32+
@interface SendableValue : Parent
33+
@end
34+
35+
SWIFT_NONSENDABLE_ASSUMED
36+
@interface NonSendableValue : Parent
37+
@end
38+
39+
//--- main.swift
40+
func testSendable<T: Sendable>(_: T) {}
41+
42+
public func test(p: Parent, v: SendableValue, ns: NonSendableValue) {
43+
testSendable(p) // expected-error {{type 'Parent' does not conform to the 'Sendable' protocol}}
44+
testSendable(v) // Ok (no diagnostics unable unavailable conformance associated with `Parent`).
45+
testSendable(ns) // expected-error {{conformance of 'NonSendableValue' to 'Sendable' is unavailable}}
46+
}
47+

0 commit comments

Comments
 (0)