Skip to content

Sema: Change behavior of implied 'Sendable' conformance #74908

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
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
3 changes: 3 additions & 0 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2714,6 +2714,9 @@ ProtocolConformance *
ASTContext::getSpecializedConformance(Type type,
NormalProtocolConformance *generic,
SubstitutionMap substitutions) {
CONDITIONAL_ASSERT(substitutions.getGenericSignature().getCanonicalSignature()
== generic->getGenericSignature().getCanonicalSignature());

// If the specialization is a no-op, use the root conformance instead.
if (collapseSpecializedConformance(type, generic, substitutions)) {
++NumCollapsedSpecializedProtocolConformances;
Expand Down
6 changes: 6 additions & 0 deletions lib/AST/ConformanceLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,12 @@ LookupConformanceInModuleRequest::evaluate(
// specialized type.
auto *normalConf = cast<NormalProtocolConformance>(conformance);
auto *conformanceDC = normalConf->getDeclContext();

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
6 changes: 5 additions & 1 deletion lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,10 @@ GenericSignature ProtocolConformance::getGenericSignature() const {
case ProtocolConformanceKind::Self:
// If we have a normal or inherited protocol conformance, look for its
// generic signature.
if (getSourceKind() == ConformanceEntryKind::Implied &&
getProtocol()->isSpecificProtocol(KnownProtocolKind::Sendable)) {
return getDeclContext()->getSelfNominalTypeDecl()->getGenericSignature();
}
return getDeclContext()->getGenericSignatureOfContext();

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

const auto extensionSig = ext->getGenericSignature();
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
2 changes: 2 additions & 0 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6055,6 +6055,8 @@ bool swift::checkSendableConformance(
}
}

if (conformance->getSourceKind() == ConformanceEntryKind::Implied)
conformanceDC = nominal;
return checkSendableInstanceStorage(nominal, conformanceDC, check);
}

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

bool allowImpliedConditionalConformance = false;
if (Proto->isSpecificProtocol(KnownProtocolKind::Sendable)) {
if (Context.LangOpts.StrictConcurrencyLevel != StrictConcurrency::Complete)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be checking for Swift 6 instead, so we don't change Swift 5 code that enabled strict checking?

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
// RUN: %target-swift-emit-silgen %s -swift-version 5

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))