Skip to content

Commit cd9f333

Browse files
committed
[ConformanceLookup] Just kidding, the compiler needs to prefer available
Sendable conformances for source compatibility. If conformance lookup always prefers the conformance from the defining module, libraries introducing unavailable Sendable conformances can break source in clients that declare retroactive Sendable conformances. Instead, still prefer the available conformance, and always diagnose the client conformance as redundant (as a warning). Then, when the retroactive conformance is removed, the errors will surface, so the programmer has to take explicit action to experience the source break.
1 parent 85b66d1 commit cd9f333

File tree

2 files changed

+35
-18
lines changed

2 files changed

+35
-18
lines changed

lib/AST/ConformanceLookupTable.cpp

+35-17
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,18 @@ void ConformanceLookupTable::ConformanceEntry::markSupersededBy(
6969
SupersededBy = entry;
7070

7171
if (diagnose) {
72+
// If an unavailable Sendable conformance is superseded by a
73+
// retroactive one in the client, we need to record this error
74+
// at the client decl context.
75+
auto *dc = getDeclContext();
76+
if (getProtocol()->isMarkerProtocol() && isFixed() &&
77+
!entry->isFixed()) {
78+
dc = entry->getDeclContext();
79+
}
80+
7281
// Record the problem in the conformance table. We'll
7382
// diagnose these in semantic analysis.
74-
table.AllSupersededDiagnostics[getDeclContext()].push_back(this);
83+
table.AllSupersededDiagnostics[dc].push_back(this);
7584
}
7685
}
7786

@@ -597,21 +606,22 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
597606
}
598607
}
599608

600-
// Unavailable Sendable conformances cannot be replaced by available ones.
601-
if (!lhs->getProtocol()->isMarkerProtocol()) {
602-
// If only one of the conformances is unconditionally available on the
603-
// current deployment target, pick that one.
604-
//
605-
// FIXME: Conformance lookup should really depend on source location for
606-
// this to be 100% correct.
607-
// FIXME: When a class and an extension with the same availability declare the
608-
// same conformance, this silently takes the class and drops the extension.
609-
if (lhs->getDeclContext()->isAlwaysAvailableConformanceContext() !=
610-
rhs->getDeclContext()->isAlwaysAvailableConformanceContext()) {
611-
return (lhs->getDeclContext()->isAlwaysAvailableConformanceContext()
612-
? Ordering::Before
613-
: Ordering::After);
614-
}
609+
// If only one of the conformances is unconditionally available on the
610+
// current deployment target, pick that one.
611+
//
612+
// FIXME: Conformance lookup should really depend on source location for
613+
// this to be 100% correct.
614+
// FIXME: When a class and an extension with the same availability declare the
615+
// same conformance, this silently takes the class and drops the extension.
616+
if (lhs->getDeclContext()->isAlwaysAvailableConformanceContext() !=
617+
rhs->getDeclContext()->isAlwaysAvailableConformanceContext()) {
618+
// Diagnose conflicting marker protocol conformances that differ in
619+
// un-availability.
620+
diagnoseSuperseded = lhs->getProtocol()->isMarkerProtocol();
621+
622+
return (lhs->getDeclContext()->isAlwaysAvailableConformanceContext()
623+
? Ordering::Before
624+
: Ordering::After);
615625
}
616626

617627
// If one entry is fixed and the other is not, we have our answer.
@@ -1127,9 +1137,17 @@ void ConformanceLookupTable::lookupConformances(
11271137
if (diagnostics) {
11281138
auto knownDiags = AllSupersededDiagnostics.find(dc);
11291139
if (knownDiags != AllSupersededDiagnostics.end()) {
1130-
for (const auto *entry : knownDiags->second) {
1140+
for (auto *entry : knownDiags->second) {
11311141
ConformanceEntry *supersededBy = entry->getSupersededBy();
11321142

1143+
// Diagnose the client conformance as superseded.
1144+
auto *definingModule = nominal->getParentModule();
1145+
if (entry->getDeclContext()->getParentModule() == definingModule &&
1146+
supersededBy->getDeclContext()->getParentModule() != definingModule) {
1147+
supersededBy = entry;
1148+
entry = entry->getSupersededBy();
1149+
}
1150+
11331151
diagnostics->push_back({entry->getProtocol(),
11341152
entry->getDeclaredLoc(),
11351153
entry->getKind(),

test/Concurrency/redundant_sendable_conformance.swift

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ extension NonSendableClass: @retroactive @unchecked Sendable {}
1313
// expected-warning@-1 {{conformance of 'NonSendableClass' to protocol 'Sendable' was already stated in the type's module 'SendableConformances'}}
1414

1515
typealias CheckNonSendableClass = RequireSendable<NonSendableClass>
16-
// expected-error@-1 {{conformance of 'NonSendableClass' to 'Sendable' is unavailable}}
1716

1817
extension SendableStruct: @retroactive @unchecked Sendable {}
1918
// expected-warning@-1 {{conformance of 'SendableStruct' to protocol 'Sendable' was already stated in the type's module 'SendableConformances'}}

0 commit comments

Comments
 (0)