Skip to content

Commit ac65650

Browse files
authored
Merge pull request #75223 from hborla/6.0-unavailable-superclass-conformance
[6.0][ConformanceLookup] Don't allow skipping inherited unavailable conformances in favor of explicit available ones.
2 parents 220f4ce + b66aed3 commit ac65650

9 files changed

+141
-48
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3127,6 +3127,9 @@ WARNING(witness_deprecated,none,
31273127
"protocol %1%select{|: %2}2",
31283128
(const ValueDecl *, Identifier, StringRef))
31293129

3130+
WARNING(unavailable_conformance,none,
3131+
"conformance of %0 to protocol %1 is already unavailable",
3132+
(Type, Identifier))
31303133
ERROR(redundant_conformance,none,
31313134
"redundant conformance of %0 to protocol %1", (Type, Identifier))
31323135
ERROR(redundant_conformance_conditional,none,

lib/AST/ConformanceLookupTable.cpp

Lines changed: 26 additions & 26 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

@@ -258,14 +267,6 @@ void ConformanceLookupTable::inheritConformances(ClassDecl *classDecl,
258267
auto addInheritedConformance = [&](ConformanceEntry *entry) {
259268
auto protocol = entry->getProtocol();
260269

261-
// Don't add unavailable conformances.
262-
if (auto dc = entry->Source.getDeclContext()) {
263-
if (auto ext = dyn_cast<ExtensionDecl>(dc)) {
264-
if (AvailableAttr::isUnavailable(ext))
265-
return;
266-
}
267-
}
268-
269270
// Don't add redundant conformances here. This is merely an
270271
// optimization; resolveConformances() would zap the duplicates
271272
// anyway.
@@ -613,30 +614,23 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
613614
// same conformance, this silently takes the class and drops the extension.
614615
if (lhs->getDeclContext()->isAlwaysAvailableConformanceContext() !=
615616
rhs->getDeclContext()->isAlwaysAvailableConformanceContext()) {
617+
// Diagnose conflicting marker protocol conformances that differ in
618+
// un-availability.
619+
diagnoseSuperseded = lhs->getProtocol()->isMarkerProtocol();
620+
616621
return (lhs->getDeclContext()->isAlwaysAvailableConformanceContext()
617622
? Ordering::Before
618623
: Ordering::After);
619624
}
620625

621626
// If one entry is fixed and the other is not, we have our answer.
622627
if (lhs->isFixed() != rhs->isFixed()) {
623-
auto isReplaceableOrMarker = [](ConformanceEntry *entry) -> bool {
624-
ConformanceEntryKind kind = entry->getRankingKind();
625-
if (isReplaceable(kind))
626-
return true;
627-
628-
// Allow replacement of an explicit conformance to a marker protocol.
629-
// (This permits redundant explicit declarations of `Sendable`.)
630-
return (kind == ConformanceEntryKind::Explicit
631-
&& entry->getProtocol()->isMarkerProtocol());
632-
};
633-
634628
// If the non-fixed conformance is not replaceable, we have a failure to
635629
// diagnose.
636630
// FIXME: We should probably diagnose if they have different constraints.
637-
diagnoseSuperseded = (lhs->isFixed() && !isReplaceableOrMarker(rhs)) ||
638-
(rhs->isFixed() && !isReplaceableOrMarker(lhs));
639-
631+
diagnoseSuperseded = (lhs->isFixed() && !isReplaceable(rhs->getRankingKind())) ||
632+
(rhs->isFixed() && !isReplaceable(lhs->getRankingKind()));
633+
640634
return lhs->isFixed() ? Ordering::Before : Ordering::After;
641635
}
642636

@@ -879,8 +873,6 @@ DeclContext *ConformanceLookupTable::getConformingContext(
879873
return nullptr;
880874
auto inheritedConformance = module->lookupConformance(
881875
superclassTy, protocol, /*allowMissing=*/false);
882-
if (inheritedConformance.hasUnavailableConformance())
883-
inheritedConformance = ProtocolConformanceRef::forInvalid();
884876
if (inheritedConformance)
885877
return superclassDecl;
886878
} while ((superclassDecl = superclassDecl->getSuperclassDecl()));
@@ -1146,9 +1138,17 @@ void ConformanceLookupTable::lookupConformances(
11461138
if (diagnostics) {
11471139
auto knownDiags = AllSupersededDiagnostics.find(dc);
11481140
if (knownDiags != AllSupersededDiagnostics.end()) {
1149-
for (const auto *entry : knownDiags->second) {
1141+
for (auto *entry : knownDiags->second) {
11501142
ConformanceEntry *supersededBy = entry->getSupersededBy();
11511143

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+
11521152
diagnostics->push_back({entry->getProtocol(),
11531153
entry->getDeclaredLoc(),
11541154
entry->getKind(),

lib/AST/ProtocolConformanceRef.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,8 @@ bool ProtocolConformanceRef::hasUnavailableConformance() const {
287287

288288
// Check whether this conformance is on an unavailable extension.
289289
auto concrete = getConcrete();
290-
auto ext = dyn_cast<ExtensionDecl>(concrete->getDeclContext());
290+
auto *dc = concrete->getRootConformance()->getDeclContext();
291+
auto ext = dyn_cast<ExtensionDecl>(dc);
291292
if (ext && AvailableAttr::isUnavailable(ext))
292293
return true;
293294

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6411,8 +6411,16 @@ ProtocolConformance *swift::deriveImplicitSendableConformance(
64116411

64126412
// Local function to form the implicit conformance.
64136413
auto formConformance = [&](const DeclAttribute *attrMakingUnavailable)
6414-
-> NormalProtocolConformance * {
6414+
-> ProtocolConformance * {
64156415
DeclContext *conformanceDC = nominal;
6416+
6417+
// FIXME: @_nonSendable should be a builtin extension macro. This behavior
6418+
// of explanding the unavailable conformance during implicit Sendable
6419+
// derivation means that clients can unknowingly ignore unavailable Sendable
6420+
// Sendable conformances from the original module added via @_nonSendable
6421+
// because they are not expanded if an explicit conformance is found via
6422+
// conformance lookup. So, if a retroactive, unchecked Sendable conformance
6423+
// is written, no redundant conformance warning is emitted.
64166424
if (attrMakingUnavailable) {
64176425
// Conformance availability is currently tied to the declaring extension.
64186426
// FIXME: This is a hack--we should give conformances real availability.
@@ -6440,6 +6448,18 @@ ProtocolConformance *swift::deriveImplicitSendableConformance(
64406448
file->getOrCreateSynthesizedFile().addTopLevelDecl(extension);
64416449

64426450
conformanceDC = extension;
6451+
6452+
// Let the conformance lookup table register the conformance
6453+
// from the extension. Otherwise, we'll end up with redundant
6454+
// conformances between the explicit conformance from the extension
6455+
// and the conformance synthesized below.
6456+
SmallVector<ProtocolConformance *, 2> conformances;
6457+
nominal->lookupConformance(proto, conformances);
6458+
for (auto conformance : conformances) {
6459+
if (conformance->getDeclContext() == conformanceDC) {
6460+
return conformance;
6461+
}
6462+
}
64436463
}
64446464

64456465
auto conformance = ctx.getNormalConformance(
@@ -6462,9 +6482,6 @@ ProtocolConformance *swift::deriveImplicitSendableConformance(
64626482
auto inheritedConformance = classModule->checkConformance(
64636483
classDecl->mapTypeIntoContext(superclass),
64646484
proto, /*allowMissing=*/false);
6465-
if (inheritedConformance.hasUnavailableConformance())
6466-
inheritedConformance = ProtocolConformanceRef::forInvalid();
6467-
64686485
if (inheritedConformance) {
64696486
inheritedConformance = inheritedConformance
64706487
.mapConformanceOutOfContext();

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6263,20 +6263,56 @@ void TypeChecker::checkConformancesInContext(IterableDeclContext *idc) {
62636263
// protocol, just warn; we'll pick up the original conformance.
62646264
auto existingModule = diag.ExistingDC->getParentModule();
62656265
auto extendedNominal = diag.ExistingDC->getSelfNominalTypeDecl();
6266-
if (existingModule != dc->getParentModule() &&
6267-
(existingModule->getName() ==
6268-
extendedNominal->getParentModule()->getName() ||
6266+
auto definingModule = extendedNominal->getParentModule()->getName();
6267+
bool conformanceInOrigModule =
6268+
(existingModule->getName() == definingModule ||
62696269
existingModule == diag.Protocol->getParentModule() ||
6270-
existingModule->getName().is("CoreGraphics"))) {
6270+
existingModule->getName().is("CoreGraphics"));
6271+
6272+
// Redundant Sendable conformances are always warnings.
6273+
auto knownProtocol = diag.Protocol->getKnownProtocolKind();
6274+
bool isSendable = knownProtocol == KnownProtocolKind::Sendable;
6275+
// Try to find an inherited Sendable conformance if there is one.
6276+
if (isSendable && !SendableConformance) {
6277+
SmallVector<ProtocolConformance *, 2> conformances;
6278+
nominal->lookupConformance(diag.Protocol, conformances);
6279+
for (auto conformance : conformances) {
6280+
if (isa<InheritedProtocolConformance>(conformance))
6281+
SendableConformance = conformance;
6282+
}
6283+
}
6284+
6285+
if ((existingModule != dc->getParentModule() && conformanceInOrigModule) ||
6286+
isSendable) {
62716287
// Warn about the conformance.
6272-
auto diagID = differentlyConditional
6273-
? diag::redundant_conformance_adhoc_conditional
6274-
: diag::redundant_conformance_adhoc;
6275-
Context.Diags.diagnose(diag.Loc, diagID, dc->getDeclaredInterfaceType(),
6276-
diag.Protocol->getName(),
6277-
existingModule->getName() ==
6278-
extendedNominal->getParentModule()->getName(),
6279-
existingModule->getName());
6288+
if (isSendable && SendableConformance &&
6289+
isa<InheritedProtocolConformance>(SendableConformance)) {
6290+
// Allow re-stated unchecked conformances to Sendable in subclasses
6291+
// as long as the inherited conformance isn't unavailable.
6292+
auto *conformance = SendableConformance->getRootConformance();
6293+
auto *decl = conformance->getDeclContext()->getAsDecl();
6294+
if (!AvailableAttr::isUnavailable(decl)) {
6295+
continue;
6296+
}
6297+
6298+
Context.Diags.diagnose(diag.Loc, diag::unavailable_conformance,
6299+
nominal->getDeclaredInterfaceType(),
6300+
diag.Protocol->getName());
6301+
} else if (existingModule == dc->getParentModule()) {
6302+
Context.Diags.diagnose(diag.Loc, diag::redundant_conformance,
6303+
nominal->getDeclaredInterfaceType(),
6304+
diag.Protocol->getName())
6305+
.limitBehavior(DiagnosticBehavior::Warning);
6306+
} else {
6307+
auto diagID = differentlyConditional
6308+
? diag::redundant_conformance_adhoc_conditional
6309+
: diag::redundant_conformance_adhoc;
6310+
Context.Diags.diagnose(diag.Loc, diagID, dc->getDeclaredInterfaceType(),
6311+
diag.Protocol->getName(),
6312+
existingModule->getName() ==
6313+
extendedNominal->getParentModule()->getName(),
6314+
existingModule->getName());
6315+
}
62806316

62816317
// Complain about any declarations in this extension whose names match
62826318
// a requirement in that protocol.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
public class NonSendableClass {}
3+
4+
@available(*, unavailable)
5+
extension NonSendableClass: @unchecked Sendable {}
6+
7+
public struct SendableStruct: Sendable {}
8+
9+
public struct AnotherSendableStruct: Sendable {}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/SendableConformances.swiftmodule -module-name SendableConformances %S/Inputs/SendableConformances.swift
3+
4+
// RUN: %target-swift-frontend -typecheck %s -verify -swift-version 6 -I %t
5+
6+
// REQUIRES: concurrency
7+
8+
import SendableConformances
9+
10+
typealias RequireSendable<T: Sendable> = T
11+
12+
extension NonSendableClass: @retroactive @unchecked Sendable {}
13+
// expected-warning@-1 {{conformance of 'NonSendableClass' to protocol 'Sendable' was already stated in the type's module 'SendableConformances'}}
14+
15+
typealias CheckNonSendableClass = RequireSendable<NonSendableClass>
16+
17+
extension SendableStruct: @retroactive @unchecked Sendable {}
18+
// 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>

test/Concurrency/sendable_checking.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,21 +102,24 @@ public actor MyActor: MyProto {
102102
}
103103

104104
// Make sure the generic signature doesn't minimize away Sendable requirements.
105-
@_nonSendable class NSClass { }
105+
class NSClass { }
106+
107+
@available(*, unavailable)
108+
extension NSClass: @unchecked Sendable {} // expected-note {{conformance of 'NSClass' to 'Sendable' has been explicitly marked unavailable here}}
106109

107110
struct WrapClass<T: NSClass> {
108111
var t: T
109112
}
110113

111114
extension WrapClass: Sendable where T: Sendable { }
112115

113-
// Make sure we don't inherit the unavailable Sendable conformance from
114-
// our superclass.
116+
// expected-warning@+2 {{conformance of 'SendableSubclass' to protocol 'Sendable' is already unavailable}}
117+
// expected-note@+1 {{'SendableSubclass' inherits conformance to protocol 'Sendable' from superclass here}}
115118
class SendableSubclass: NSClass, @unchecked Sendable { }
116119

117120
@available(SwiftStdlib 5.1, *)
118121
func testSubclassing(obj: SendableSubclass) async {
119-
acceptCV(obj) // okay!
122+
acceptCV(obj) // expected-warning {{conformance of 'NSClass' to 'Sendable' is unavailable; this is an error in the Swift 6 language mode}}
120123
}
121124

122125

test/Concurrency/sendable_conformance_checking.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ extension SendableExtSub: @unchecked Sendable {}
180180

181181
// Still want to know about same-class redundancy
182182
class MultiConformance: @unchecked Sendable {} // expected-note {{'MultiConformance' declares conformance to protocol 'Sendable' here}}
183-
extension MultiConformance: @unchecked Sendable {} // expected-error {{redundant conformance of 'MultiConformance' to protocol 'Sendable'}}
183+
extension MultiConformance: @unchecked Sendable {} // expected-warning {{redundant conformance of 'MultiConformance' to protocol 'Sendable'}}
184184

185185
@available(SwiftStdlib 5.1, *)
186186
actor MyActor {

0 commit comments

Comments
 (0)