From d868e68cd24dd0549386a767f00f3513c02e8b74 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 19 Nov 2025 16:37:17 -0800 Subject: [PATCH] [Concurrency] Allow conditionally conforming to `Sendable` when conformance is suppressed For consistency with invertible protocols using `~Sendable` should only prohibit use of unconditional extensions. For example: ```swift struct G: ~Sendable {} ``` The following (unconditional) extension is rejected: ``` extension G: Sendable {} // error: cannot both conform to and suppress conformance to 'Sendable' ``` But conditional on `T` is accepted: ``` extension G: Sendable where T: Sendable {} // Ok! ``` --- lib/Sema/TypeCheckConcurrency.cpp | 27 +++++++++++----- test/ModuleInterface/tilde_sendable.swift | 15 +++++++++ test/Sema/tilde_sendable.swift | 39 +++++++++++++++++++++++ 3 files changed, 73 insertions(+), 8 deletions(-) diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index 2f89d2d19daf2..0782511990a3c 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -7180,14 +7180,6 @@ static bool checkSendableInstanceStorage( } } - if (nominal->suppressesConformance(KnownProtocolKind::Sendable)) { - auto *conformanceDecl = dc->getAsDecl() ? dc->getAsDecl() : nominal; - if (!isImplicitSendableCheck(check)) { - conformanceDecl->diagnose(diag::non_sendable_type_suppressed); - } - return true; - } - // Stored properties of structs and classes must have // Sendable-conforming types. class Visitor: public StorageVisitor { @@ -7462,6 +7454,25 @@ bool swift::checkSendableConformance( // and not some (possibly constrained) extension. if (wasImplied) conformanceDC = nominal; + + // Sendable supression allows conditional conformances only. + if (nominal->suppressesConformance(KnownProtocolKind::Sendable)) { + bool hasUnconditionalConformance = false; + if (auto *normalConf = dyn_cast(conformance)) { + hasUnconditionalConformance = + normalConf->getConditionalRequirements().empty(); + } + + if (hasUnconditionalConformance) { + if (!isImplicitSendableCheck(check)) { + auto *conformanceDecl = + conformanceDC->getAsDecl() ? conformanceDC->getAsDecl() : nominal; + conformanceDecl->diagnose(diag::non_sendable_type_suppressed); + } + return true; + } + } + return checkSendableInstanceStorage(nominal, conformanceDC, check); } diff --git a/test/ModuleInterface/tilde_sendable.swift b/test/ModuleInterface/tilde_sendable.swift index 73b6710d55cc1..f9e4c5cc55605 100644 --- a/test/ModuleInterface/tilde_sendable.swift +++ b/test/ModuleInterface/tilde_sendable.swift @@ -33,3 +33,18 @@ protocol P { public struct S: P, ~Sendable { public let x: Int } + +// CHECK: #if compiler(>=5.3) && $TildeSendable +// CHECK: public struct B : ~Swift.Sendable { +// CHECK: } +// CHECK: #else +// CHECK: public struct B { +// CHECK: } +// CHECK: #endif +public struct B: ~Sendable { +} + +// CHECK: extension Library.B : Swift.Sendable where T : Swift.Sendable { +// CHECK: } +extension B: Sendable where T: Sendable { +} diff --git a/test/Sema/tilde_sendable.swift b/test/Sema/tilde_sendable.swift index 65d5b5da38d81..c23d82eab52e0 100644 --- a/test/Sema/tilde_sendable.swift +++ b/test/Sema/tilde_sendable.swift @@ -52,3 +52,42 @@ do { check(NonSendable()) // expected-warning {{type 'NonSendable' does not conform to the 'Sendable' protocol}} check(NoInference()) // Ok } + +func takesSendable(_: T) {} + +class MyValue {} // expected-note 2 {{class 'MyValue' does not conform to the 'Sendable' protocol}} + +public struct D: ~Sendable { +} + +extension D: Sendable {} // expected-error {{cannot both conform to and suppress conformance to 'Sendable'}} + +takesSendable(D()) + +public struct F: ~Sendable { + let x: T +} + +extension F: Sendable where T: Sendable { } + +takesSendable(F(x: 42)) + +public struct G: ~Sendable { // expected-note {{making generic parameter 'U' conform to the 'Sendable' protocol}} + let t: T + let u: U // expected-warning {{stored property 'u' of 'Sendable'-conforming generic struct 'G' has non-Sendable type 'U'}} +} + +extension G: Sendable where T: Sendable { } + +takesSendable(G(t: "", u: 42)) +takesSendable(G(t: MyValue(), u: 0)) // expected-warning {{type 'MyValue' does not conform to the 'Sendable' protocol}} + +public struct H: ~Sendable { + let t: T + let u: U +} + +extension H: Sendable where T: Sendable, U: Sendable { } + +takesSendable(H(t: "", u: 42)) +takesSendable(H(t: "", u: MyValue())) // expected-warning {{type 'MyValue' does not conform to the 'Sendable' protocol}}