Skip to content

[6.0] Change behavior of implied 'Sendable' conformance #74914

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
10 changes: 10 additions & 0 deletions lib/AST/ConformanceLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,16 @@ LookupConformanceInModuleRequest::evaluate(
// specialized type.
auto *normalConf = cast<NormalProtocolConformance>(conformance);
auto *conformanceDC = normalConf->getDeclContext();

// In -swift-version 5 mode, a conditional conformance to a protocol can imply
// a Sendable conformance. The implied conformance is unconditional so it uses
// the generic signature of the nominal type and not the generic signature of
// the extension that declared the (implying) conditional conformance.
if (normalConf->getSourceKind() == ConformanceEntryKind::Implied &&
normalConf->getProtocol()->isSpecificProtocol(KnownProtocolKind::Sendable)) {
conformanceDC = conformanceDC->getSelfNominalTypeDecl();
}

auto subMap = type->getContextSubstitutionMap(mod, conformanceDC);
return ProtocolConformanceRef(
ctx.getSpecializedConformance(type, normalConf, subMap));
Expand Down
16 changes: 15 additions & 1 deletion lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,16 @@ GenericSignature ProtocolConformance::getGenericSignature() const {
case ProtocolConformanceKind::Self:
// If we have a normal or inherited protocol conformance, look for its
// generic signature.

// In -swift-version 5 mode, a conditional conformance to a protocol can imply
// a Sendable conformance. The implied conformance is unconditional so it uses
// the generic signature of the nominal type and not the generic signature of
// the extension that declared the (implying) conditional conformance.
if (getSourceKind() == ConformanceEntryKind::Implied &&
getProtocol()->isSpecificProtocol(KnownProtocolKind::Sendable)) {
return getDeclContext()->getSelfNominalTypeDecl()->getGenericSignature();
}

return getDeclContext()->getGenericSignatureOfContext();

case ProtocolConformanceKind::Builtin:
Expand Down Expand Up @@ -405,7 +415,11 @@ ConditionalRequirementsRequest::evaluate(Evaluator &evaluator,
return {};
}

const auto extensionSig = ext->getGenericSignature();
// In -swift-version 5 mode, a conditional conformance to a protocol can imply
// a Sendable conformance. We ask the conformance for its generic signature,
// which will always be the generic signature of `ext` except in this case,
// where it's the generic signature of the extended nominal.
const auto extensionSig = NPC->getGenericSignature();

// The extension signature should be a superset of the type signature, meaning
// every thing in the type signature either is included too or is implied by
Expand Down
6 changes: 6 additions & 0 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6040,6 +6040,12 @@ bool swift::checkSendableConformance(
}
}

// In -swift-version 5 mode, a conditional conformance to a protocol can imply
// a Sendable conformance. The implied conformance is unconditional, so check
// the storage for sendability as if the conformance was declared on the nominal,
// and not some (possibly constrained) extension.
if (conformance->getSourceKind() == ConformanceEntryKind::Implied)
conformanceDC = nominal;
return checkSendableInstanceStorage(nominal, conformanceDC, check);
}

Expand Down
12 changes: 11 additions & 1 deletion lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2414,8 +2414,18 @@ checkIndividualConformance(NormalProtocolConformance *conformance) {
ComplainLoc, diag::unchecked_conformance_not_special, ProtoType);
}

bool allowImpliedConditionalConformance = false;
if (Proto->isSpecificProtocol(KnownProtocolKind::Sendable)) {
// In -swift-version 5 mode, a conditional conformance to a protocol can imply
// a Sendable conformance.
if (!Context.LangOpts.isSwiftVersionAtLeast(6))
allowImpliedConditionalConformance = true;
} else if (Proto->isMarkerProtocol()) {
allowImpliedConditionalConformance = true;
}

if (conformance->getSourceKind() == ConformanceEntryKind::Implied &&
!Proto->isMarkerProtocol()) {
!allowImpliedConditionalConformance) {
// We've got something like:
//
// protocol Foo : Proto {}
Expand Down
23 changes: 23 additions & 0 deletions test/Concurrency/implied_sendable_conformance_swift5.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// RUN: %target-typecheck-verify-swift -swift-version 5 -strict-concurrency=complete
// RUN: %target-swift-emit-silgen %s -swift-version 5 -strict-concurrency=complete

protocol P: Sendable {}
protocol Q: Sendable {}

struct One<T> { // expected-note {{consider making generic parameter 'T' conform to the 'Sendable' protocol}}
var t: T // expected-warning {{stored property 't' of 'Sendable'-conforming generic struct 'One' has non-sendable type 'T'; this is an error in the Swift 6 language mode}}
}

extension One: P where T: P {}

struct Both<T> { // expected-note {{consider making generic parameter 'T' conform to the 'Sendable' protocol}}
var t: T // expected-warning {{stored property 't' of 'Sendable'-conforming generic struct 'Both' has non-sendable type 'T'; this is an error in the Swift 6 language mode}}
}

extension Both: P where T: P {}
extension Both: Q where T: Q {}

func takesSendable<T: Sendable>(_: T) {}

takesSendable(One<Int>(t: 3))
takesSendable(Both<Int>(t: 3))
27 changes: 27 additions & 0 deletions test/Concurrency/implied_sendable_conformance_swift6.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// RUN: %target-typecheck-verify-swift -swift-version 6

protocol P: Sendable {}
protocol Q: Sendable {}

struct One<T> { // expected-note {{consider making generic parameter 'T' conform to the 'Sendable' protocol}}
var t: T // expected-error {{stored property 't' of 'Sendable'-conforming generic struct 'One' has non-sendable type 'T'}}
}

extension One: P where T: P {}
// expected-error@-1 {{conditional conformance of type 'One<T>' to protocol 'P' does not imply conformance to inherited protocol 'Sendable'}}
// expected-note@-2 {{did you mean to explicitly state the conformance like 'extension One: Sendable where ...'}}

struct Both<T> { // expected-note {{consider making generic parameter 'T' conform to the 'Sendable' protocol}}
var t: T // expected-error {{stored property 't' of 'Sendable'-conforming generic struct 'Both' has non-sendable type 'T'}}
}

extension Both: P where T: P {}
// expected-error@-1 {{conditional conformance of type 'Both<T>' to protocol 'P' does not imply conformance to inherited protocol 'Sendable'}}
// expected-note@-2 {{did you mean to explicitly state the conformance like 'extension Both: Sendable where ...'}}

extension Both: Q where T: Q {}

func takesSendable<T: Sendable>(_: T) {}

takesSendable(One<Int>(t: 3))
takesSendable(Both<Int>(t: 3))