Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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; }
Expand All @@ -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();
}
Expand Down
6 changes: 5 additions & 1 deletion lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8270,6 +8270,7 @@ swift::getInheritedForPrinting(
// Collect synthesized conformances.
llvm::SetVector<ProtocolDecl *> protocols;
llvm::TinyPtrVector<ProtocolDecl *> uncheckedProtocols;
llvm::TinyPtrVector<ProtocolDecl *> suppressedProtocols;
for (auto attr : decl->getAttrs().getAttributes<SynthesizedProtocolAttr>()) {
if (auto *proto = attr->getProtocol()) {
// FIXME: Reconstitute inverses here
Expand All @@ -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
Expand All @@ -8319,7 +8323,7 @@ swift::getInheritedForPrinting(
if (isUnchecked)
options |= ProtocolConformanceFlags::Unchecked;
Results.push_back({TypeLoc::withoutLoc(proto->getDeclaredInterfaceType()),
options});
options, isSuppressed});
}
}

Expand Down
7 changes: 7 additions & 0 deletions lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion lib/AST/NameLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

Expand Down
33 changes: 24 additions & 9 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,8 @@ void ClangImporter::Implementation::addSynthesizedTypealias(

void ClangImporter::Implementation::addSynthesizedProtocolAttrs(
NominalTypeDecl *nominal,
ArrayRef<KnownProtocolKind> synthesizedProtocolAttrs, bool isUnchecked) {
ArrayRef<KnownProtocolKind> synthesizedProtocolAttrs, bool isUnchecked,
bool isSuppressed) {
auto &ctx = nominal->getASTContext();

for (auto kind : synthesizedProtocolAttrs) {
Expand All @@ -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));
}
}

Expand Down Expand Up @@ -6640,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
Expand Down Expand Up @@ -8948,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) {
//
Expand Down Expand Up @@ -9040,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))
Expand Down Expand Up @@ -9107,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));

Expand Down Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion lib/ClangImporter/ImporterImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1264,7 +1264,8 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
void addSynthesizedProtocolAttrs(
NominalTypeDecl *nominal,
ArrayRef<KnownProtocolKind> synthesizedProtocolAttrs,
bool isUnchecked = false);
bool isUnchecked = false,
bool isSuppressed = false);

void makeComputed(AbstractStorageDecl *storage, AccessorDecl *getter,
AccessorDecl *setter);
Expand Down
7 changes: 7 additions & 0 deletions lib/Sema/TypeCheckRequestFunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
47 changes: 47 additions & 0 deletions test/Concurrency/tilde_sendable_objc.swift
Original file line number Diff line number Diff line change
@@ -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}}
}