Skip to content

Commit e28670d

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. (cherry picked from commit b139770)
1 parent 8159d20 commit e28670d

File tree

3 files changed

+43
-18
lines changed

3 files changed

+43
-18
lines changed

lib/AST/ConformanceLookupTable.cpp

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,18 @@ void ConformanceLookupTable::ConformanceEntry::markSupersededBy(
6868
SupersededBy = entry;
6969

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

@@ -596,21 +605,22 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
596605
}
597606
}
598607

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

616626
// If one entry is fixed and the other is not, we have our answer.
@@ -1128,9 +1138,17 @@ void ConformanceLookupTable::lookupConformances(
11281138
if (diagnostics) {
11291139
auto knownDiags = AllSupersededDiagnostics.find(dc);
11301140
if (knownDiags != AllSupersededDiagnostics.end()) {
1131-
for (const auto *entry : knownDiags->second) {
1141+
for (auto *entry : knownDiags->second) {
11321142
ConformanceEntry *supersededBy = entry->getSupersededBy();
11331143

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

test/Concurrency/Inputs/SendableConformances.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ public class NonSendableClass {}
55
extension NonSendableClass: @unchecked Sendable {}
66

77
public struct SendableStruct: Sendable {}
8+
9+
public struct AnotherSendableStruct: Sendable {}

test/Concurrency/redundant_sendable_conformance.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ 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'}}
19+
20+
@available(*, unavailable)
21+
extension AnotherSendableStruct: @retroactive @unchecked Sendable {}
22+
// expected-warning@-1 {{conformance of 'AnotherSendableStruct' to protocol 'Sendable' was already stated in the type's module 'SendableConformances'}}
23+
24+
typealias CheckAnotherSendableStruct = RequireSendable<AnotherSendableStruct>

0 commit comments

Comments
 (0)