[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<T>: ~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!
```
This commit is contained in:
Pavel Yaskevich
2025-11-19 16:37:17 -08:00
parent 5cc8264d8d
commit d868e68cd2
3 changed files with 73 additions and 8 deletions

View File

@@ -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<NormalProtocolConformance>(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);
}

View File

@@ -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<T> : ~Swift.Sendable {
// CHECK: }
// CHECK: #else
// CHECK: public struct B<T> {
// CHECK: }
// CHECK: #endif
public struct B<T>: ~Sendable {
}
// CHECK: extension Library.B : Swift.Sendable where T : Swift.Sendable {
// CHECK: }
extension B: Sendable where T: Sendable {
}

View File

@@ -52,3 +52,42 @@ do {
check(NonSendable()) // expected-warning {{type 'NonSendable' does not conform to the 'Sendable' protocol}}
check(NoInference()) // Ok
}
func takesSendable<T: Sendable>(_: 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<T>: ~Sendable {
let x: T
}
extension F: Sendable where T: Sendable { }
takesSendable(F(x: 42))
public struct G<T, U>: ~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<T, U>: ~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}}