[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
This commit is contained in:
Pavel Yaskevich
2025-10-24 15:24:19 +09:00
parent 2c1329e861
commit 65599ce1f1
6 changed files with 83 additions and 6 deletions

View File

@@ -3306,6 +3306,13 @@ DeclAttributes::getEffectiveSendableAttr() const {
if (auto sendableAttr = getAttribute<SendableAttr>())
return sendableAttr;
// ~Sendable on declarations imported from Objective-C.
for (auto *attr : getAttributes<SynthesizedProtocolAttr>()) {
if (attr->getProtocol()->isSpecificProtocol(KnownProtocolKind::Sendable) &&
attr->isSuppressed())
return nullptr;
}
return assumedAttr;
}

View File

@@ -4168,7 +4168,7 @@ swift::getDirectlyInheritedNominalTypeDecls(
if (attr->isUnchecked())
attributes.uncheckedLoc = loc;
result.push_back({attr->getProtocol(), loc, /*inheritedTypeRepr=*/nullptr,
attributes, /*isSuppressed=*/false});
attributes, attr->isSuppressed()});
}
// Else we have access to this information on the where clause.

View File

@@ -1248,6 +1248,8 @@ void NominalTypeDecl::prepareConformanceTable() const {
// Add protocols for any synthesized protocol attributes.
for (auto attr : getAttrs().getAttributes<SynthesizedProtocolAttr>()) {
if (attr->isSuppressed())
continue;
addSynthesized(attr->getProtocol());
}

View File

@@ -6641,7 +6641,7 @@ static bool conformsToProtocolInOriginalModule(NominalTypeDecl *nominal,
for (auto attr : nominal->getAttrs().getAttributes<SynthesizedProtocolAttr>()) {
auto *otherProto = attr->getProtocol();
if (otherProto == proto || otherProto->inheritsFrom(proto))
return true;
return !attr->isSuppressed();
}
// Only consider extensions from the original module...or from an overlay
@@ -8949,6 +8949,7 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
std::optional<const clang::SwiftAttrAttr *> seenMainActorAttr;
const clang::SwiftAttrAttr *seenMutabilityAttr = nullptr;
llvm::SmallSet<ProtocolDecl *, 4> conformancesSeen;
const clang::SwiftAttrAttr *seenSendableSuppressionAttr = nullptr;
auto importAttrsFromDecl = [&](const clang::NamedDecl *ClangDecl) {
//
@@ -9041,6 +9042,18 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
nominal->registerProtocolConformance(conformance, /*synthesized=*/true);
}
if (swiftAttr->getAttribute() == "~Sendable") {
auto *nominal = dyn_cast<NominalTypeDecl>(MappedDecl);
if (!nominal)
continue;
seenSendableSuppressionAttr = swiftAttr;
addSynthesizedProtocolAttrs(nominal, {KnownProtocolKind::Sendable},
/*isUnchecked=*/false,
/*isSuppressed=*/true);
continue;
}
if (swiftAttr->getAttribute() == "sending") {
// Swallow this if the feature is not enabled.
if (!SwiftContext.LangOpts.hasFeature(Feature::SendingArgsAndResults))
@@ -9108,10 +9121,11 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) {
MappedDecl->getAttrs().removeAttribute(attr);
// Some types have an implicit '@Sendable' attribute.
if (ClangDecl->hasAttr<clang::SwiftNewTypeAttr>() ||
ClangDecl->hasAttr<clang::EnumExtensibilityAttr>() ||
ClangDecl->hasAttr<clang::FlagEnumAttr>() ||
ClangDecl->hasAttr<clang::NSErrorDomainAttr>())
if ((ClangDecl->hasAttr<clang::SwiftNewTypeAttr>() ||
ClangDecl->hasAttr<clang::EnumExtensibilityAttr>() ||
ClangDecl->hasAttr<clang::FlagEnumAttr>() ||
ClangDecl->hasAttr<clang::NSErrorDomainAttr>()) &&
!seenSendableSuppressionAttr)
MappedDecl->addAttribute(new (SwiftContext)
SendableAttr(/*isImplicit=*/true));

View File

@@ -172,6 +172,13 @@ bool SuppressesConformanceRequest::evaluate(Evaluator &evaluator,
if (other == kp)
return true;
}
for (auto *attr :
nominal->getAttrs().getAttributes<SynthesizedProtocolAttr>()) {
if (attr->getProtocol()->isSpecificProtocol(kp) && attr->isSuppressed())
return true;
}
return false;
}

View File

@@ -0,0 +1,47 @@
// RUN: %empty-directory(%t/src)
// RUN: %empty-directory(%t/sdk)
// RUN: split-file %s %t/src
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck %t/src/main.swift \
// RUN: -import-objc-header %t/src/Test.h \
// RUN: -swift-version 6 \
// RUN: -enable-experimental-feature TildeSendable \
// RUN: -module-name main -I %t -verify -verify-ignore-unrelated
// REQUIRES: objc_interop
// REQUIRES: swift_feature_TildeSendable
//--- Test.h
#define SWIFT_SENDABLE __attribute__((__swift_attr__("@Sendable")))
#define SWIFT_NONSENDABLE_ASSUMED __attribute__((__swift_attr__("@_nonSendable(_assumed)")))
#define SWIFT_SUPPRESS_SENDABLE __attribute__((__swift_attr__("~Sendable")))
@import Foundation;
// Test that `~Sendable` superseeds `@_nonSendable(_assumed)` on classes.
SWIFT_NONSENDABLE_ASSUMED
SWIFT_SUPPRESS_SENDABLE
@interface Parent : NSObject
@end
// Test that `Sendable` superseeds `@_nonSendable(_assumed)` and `~Sendable` from the parent.
SWIFT_NONSENDABLE_ASSUMED
SWIFT_SENDABLE
@interface SendableValue : Parent
@end
SWIFT_NONSENDABLE_ASSUMED
@interface NonSendableValue : Parent
@end
//--- main.swift
func testSendable<T: Sendable>(_: T) {}
public func test(p: Parent, v: SendableValue, ns: NonSendableValue) {
testSendable(p) // expected-error {{type 'Parent' does not conform to the 'Sendable' protocol}}
testSendable(v) // Ok (no diagnostics unable unavailable conformance associated with `Parent`).
testSendable(ns) // expected-error {{conformance of 'NonSendableValue' to 'Sendable' is unavailable}}
}