diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index 9b563fe3cc366..da35e02ee840b 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -256,8 +256,9 @@ class DeclAttribute : public AttributeBase { kind : NumExternKindBits ); - SWIFT_INLINE_BITFIELD(SynthesizedProtocolAttr, DeclAttribute, 1, - isUnchecked : 1 + SWIFT_INLINE_BITFIELD(SynthesizedProtocolAttr, DeclAttribute, 2, + isUnchecked : 1, + isSuppressed: 1 ); SWIFT_INLINE_BITFIELD(ObjCImplementationAttr, DeclAttribute, 3, @@ -1754,12 +1755,13 @@ class SynthesizedProtocolAttr : public DeclAttribute { public: SynthesizedProtocolAttr(ProtocolDecl *protocol, LazyConformanceLoader *Loader, - bool isUnchecked) + bool isUnchecked, bool isSuppressed) : DeclAttribute(DeclAttrKind::SynthesizedProtocol, SourceLoc(), SourceRange(), /*Implicit=*/true), Loader(Loader), protocol(protocol) { Bits.SynthesizedProtocolAttr.isUnchecked = unsigned(isUnchecked); + Bits.SynthesizedProtocolAttr.isSuppressed = unsigned(isSuppressed); } /// Retrieve the known protocol kind naming the protocol to be @@ -1772,6 +1774,10 @@ class SynthesizedProtocolAttr : public DeclAttribute { return bool(Bits.SynthesizedProtocolAttr.isUnchecked); } + bool isSuppressed() const { + return bool(Bits.SynthesizedProtocolAttr.isSuppressed); + } + /// Retrieve the lazy loader that will be used to populate the /// synthesized conformance. LazyConformanceLoader *getLazyLoader() const { return Loader; } @@ -1782,12 +1788,13 @@ class SynthesizedProtocolAttr : public DeclAttribute { SynthesizedProtocolAttr *clone(ASTContext &ctx) const { return new (ctx) SynthesizedProtocolAttr( - protocol, getLazyLoader(), isUnchecked()); + protocol, getLazyLoader(), isUnchecked(), isSuppressed()); } bool isEquivalent(const SynthesizedProtocolAttr *other, Decl *attachedTo) const { return isUnchecked() == other->isUnchecked() + && isSuppressed() == other->isSuppressed() && getProtocol() == other->getProtocol() && getLazyLoader() == other->getLazyLoader(); } diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 3f72179aeb292..d9abef858c0d2 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -8270,6 +8270,7 @@ swift::getInheritedForPrinting( // Collect synthesized conformances. llvm::SetVector protocols; llvm::TinyPtrVector uncheckedProtocols; + llvm::TinyPtrVector suppressedProtocols; for (auto attr : decl->getAttrs().getAttributes()) { if (auto *proto = attr->getProtocol()) { // FIXME: Reconstitute inverses here @@ -8289,12 +8290,15 @@ swift::getInheritedForPrinting( protocols.insert(proto); if (attr->isUnchecked()) uncheckedProtocols.push_back(proto); + if (attr->isSuppressed()) + suppressedProtocols.push_back(proto); } } for (size_t i = 0; i < protocols.size(); i++) { auto proto = protocols[i]; bool isUnchecked = llvm::is_contained(uncheckedProtocols, proto); + bool isSuppressed = llvm::is_contained(suppressedProtocols, proto); if (!options.shouldPrint(proto)) { // If private stdlib protocols are skipped and this is a private stdlib @@ -8319,7 +8323,7 @@ swift::getInheritedForPrinting( if (isUnchecked) options |= ProtocolConformanceFlags::Unchecked; Results.push_back({TypeLoc::withoutLoc(proto->getDeclaredInterfaceType()), - options}); + options, isSuppressed}); } } diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index b234e816e43a2..c457c606e68fc 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -3306,6 +3306,13 @@ DeclAttributes::getEffectiveSendableAttr() const { if (auto sendableAttr = getAttribute()) return sendableAttr; + // ~Sendable on declarations imported from Objective-C. + for (auto *attr : getAttributes()) { + if (attr->getProtocol()->isSpecificProtocol(KnownProtocolKind::Sendable) && + attr->isSuppressed()) + return nullptr; + } + return assumedAttr; } diff --git a/lib/AST/NameLookup.cpp b/lib/AST/NameLookup.cpp index d39075dc14d8f..7af97a005b4ed 100644 --- a/lib/AST/NameLookup.cpp +++ b/lib/AST/NameLookup.cpp @@ -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. diff --git a/lib/AST/ProtocolConformance.cpp b/lib/AST/ProtocolConformance.cpp index f6d072a88478a..0c581d50db4b6 100644 --- a/lib/AST/ProtocolConformance.cpp +++ b/lib/AST/ProtocolConformance.cpp @@ -1248,6 +1248,8 @@ void NominalTypeDecl::prepareConformanceTable() const { // Add protocols for any synthesized protocol attributes. for (auto attr : getAttrs().getAttributes()) { + if (attr->isSuppressed()) + continue; addSynthesized(attr->getProtocol()); } diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 98f88555c238c..87ba839118f43 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -503,7 +503,8 @@ void ClangImporter::Implementation::addSynthesizedTypealias( void ClangImporter::Implementation::addSynthesizedProtocolAttrs( NominalTypeDecl *nominal, - ArrayRef synthesizedProtocolAttrs, bool isUnchecked) { + ArrayRef synthesizedProtocolAttrs, bool isUnchecked, + bool isSuppressed) { auto &ctx = nominal->getASTContext(); for (auto kind : synthesizedProtocolAttrs) { @@ -512,7 +513,7 @@ void ClangImporter::Implementation::addSynthesizedProtocolAttrs( // ctx.getProtocol(kind) != nulltpr which would be nice. if (auto proto = ctx.getProtocol(kind)) nominal->addAttribute( - new (ctx) SynthesizedProtocolAttr(proto, this, isUnchecked)); + new (ctx) SynthesizedProtocolAttr(proto, this, isUnchecked, isSuppressed)); } } @@ -6640,7 +6641,7 @@ static bool conformsToProtocolInOriginalModule(NominalTypeDecl *nominal, for (auto attr : nominal->getAttrs().getAttributes()) { 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 @@ -8948,6 +8949,7 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) { std::optional seenMainActorAttr; const clang::SwiftAttrAttr *seenMutabilityAttr = nullptr; llvm::SmallSet conformancesSeen; + const clang::SwiftAttrAttr *seenSendableSuppressionAttr = nullptr; auto importAttrsFromDecl = [&](const clang::NamedDecl *ClangDecl) { // @@ -9040,6 +9042,18 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) { nominal->registerProtocolConformance(conformance, /*synthesized=*/true); } + if (swiftAttr->getAttribute() == "~Sendable") { + auto *nominal = dyn_cast(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)) @@ -9107,10 +9121,11 @@ ClangImporter::Implementation::importSwiftAttrAttributes(Decl *MappedDecl) { MappedDecl->getAttrs().removeAttribute(attr); // Some types have an implicit '@Sendable' attribute. - if (ClangDecl->hasAttr() || - ClangDecl->hasAttr() || - ClangDecl->hasAttr() || - ClangDecl->hasAttr()) + if ((ClangDecl->hasAttr() || + ClangDecl->hasAttr() || + ClangDecl->hasAttr() || + ClangDecl->hasAttr()) && + !seenSendableSuppressionAttr) MappedDecl->addAttribute(new (SwiftContext) SendableAttr(/*isImplicit=*/true)); @@ -9470,8 +9485,8 @@ void ClangImporter::Implementation::addExplicitProtocolConformance( decl->getDeclaredInterfaceType(), conformsToValue); } - decl->addAttribute(new (SwiftContext) - SynthesizedProtocolAttr(protocol, this, false)); + decl->addAttribute(new (SwiftContext) SynthesizedProtocolAttr( + protocol, this, /*isUnchecked=*/false, /*isSuppressed=*/false)); } else { HeaderLoc attrLoc((conformsToAttr)->getLocation()); diagnose(attrLoc, diag::conforms_to_not_protocol, result, diff --git a/lib/ClangImporter/ImporterImpl.h b/lib/ClangImporter/ImporterImpl.h index ed4c18dd1ca7a..5fdbc52a32372 100644 --- a/lib/ClangImporter/ImporterImpl.h +++ b/lib/ClangImporter/ImporterImpl.h @@ -1264,7 +1264,8 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation void addSynthesizedProtocolAttrs( NominalTypeDecl *nominal, ArrayRef synthesizedProtocolAttrs, - bool isUnchecked = false); + bool isUnchecked = false, + bool isSuppressed = false); void makeComputed(AbstractStorageDecl *storage, AccessorDecl *getter, AccessorDecl *setter); diff --git a/lib/Sema/TypeCheckRequestFunctions.cpp b/lib/Sema/TypeCheckRequestFunctions.cpp index 9d24c72b25441..199ae697e79d3 100644 --- a/lib/Sema/TypeCheckRequestFunctions.cpp +++ b/lib/Sema/TypeCheckRequestFunctions.cpp @@ -172,6 +172,13 @@ bool SuppressesConformanceRequest::evaluate(Evaluator &evaluator, if (other == kp) return true; } + + for (auto *attr : + nominal->getAttrs().getAttributes()) { + if (attr->getProtocol()->isSpecificProtocol(kp) && attr->isSuppressed()) + return true; + } + return false; } diff --git a/test/Concurrency/tilde_sendable_objc.swift b/test/Concurrency/tilde_sendable_objc.swift new file mode 100644 index 0000000000000..02e0affd7a4b8 --- /dev/null +++ b/test/Concurrency/tilde_sendable_objc.swift @@ -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) {} + +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}} +} +