Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -3147,6 +3147,9 @@ WARNING(witness_deprecated,none,
"protocol %1%select{|: %2}2",
(const ValueDecl *, Identifier, StringRef))

WARNING(unavailable_conformance,none,
"conformance of %0 to protocol %1 is already unavailable",
(Type, Identifier))
ERROR(redundant_conformance,none,
"redundant conformance of %0 to protocol %1", (Type, Identifier))
ERROR(redundant_conformance_conditional,none,
Expand Down
54 changes: 18 additions & 36 deletions lib/AST/ConformanceLookupTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,14 +259,6 @@ void ConformanceLookupTable::inheritConformances(ClassDecl *classDecl,
auto addInheritedConformance = [&](ConformanceEntry *entry) {
auto protocol = entry->getProtocol();

// Don't add unavailable conformances.
if (auto dc = entry->Source.getDeclContext()) {
if (auto ext = dyn_cast<ExtensionDecl>(dc)) {
if (AvailableAttr::isUnavailable(ext))
return;
}
}

// Don't add redundant conformances here. This is merely an
// optimization; resolveConformances() would zap the duplicates
// anyway.
Expand Down Expand Up @@ -605,39 +597,31 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
}
}

// If only one of the conformances is unconditionally available on the
// current deployment target, pick that one.
//
// FIXME: Conformance lookup should really depend on source location for
// this to be 100% correct.
// FIXME: When a class and an extension with the same availability declare the
// same conformance, this silently takes the class and drops the extension.
if (lhs->getDeclContext()->isAlwaysAvailableConformanceContext() !=
rhs->getDeclContext()->isAlwaysAvailableConformanceContext()) {
return (lhs->getDeclContext()->isAlwaysAvailableConformanceContext()
? Ordering::Before
: Ordering::After);
// Unavailable Sendable conformances cannot be replaced by available ones.
if (!lhs->getProtocol()->isMarkerProtocol()) {
// If only one of the conformances is unconditionally available on the
// current deployment target, pick that one.
//
// FIXME: Conformance lookup should really depend on source location for
// this to be 100% correct.
// FIXME: When a class and an extension with the same availability declare the
// same conformance, this silently takes the class and drops the extension.
if (lhs->getDeclContext()->isAlwaysAvailableConformanceContext() !=
rhs->getDeclContext()->isAlwaysAvailableConformanceContext()) {
return (lhs->getDeclContext()->isAlwaysAvailableConformanceContext()
? Ordering::Before
: Ordering::After);
}
}

// If one entry is fixed and the other is not, we have our answer.
if (lhs->isFixed() != rhs->isFixed()) {
auto isReplaceableOrMarker = [](ConformanceEntry *entry) -> bool {
ConformanceEntryKind kind = entry->getRankingKind();
if (isReplaceable(kind))
return true;

// Allow replacement of an explicit conformance to a marker protocol.
// (This permits redundant explicit declarations of `Sendable`.)
return (kind == ConformanceEntryKind::Explicit
&& entry->getProtocol()->isMarkerProtocol());
};

// If the non-fixed conformance is not replaceable, we have a failure to
// diagnose.
// FIXME: We should probably diagnose if they have different constraints.
diagnoseSuperseded = (lhs->isFixed() && !isReplaceableOrMarker(rhs)) ||
(rhs->isFixed() && !isReplaceableOrMarker(lhs));
diagnoseSuperseded = (lhs->isFixed() && !isReplaceable(rhs->getRankingKind())) ||
(rhs->isFixed() && !isReplaceable(lhs->getRankingKind()));

return lhs->isFixed() ? Ordering::Before : Ordering::After;
}

Expand Down Expand Up @@ -879,8 +863,6 @@ DeclContext *ConformanceLookupTable::getConformingContext(
return nullptr;
auto inheritedConformance = ModuleDecl::lookupConformance(
superclassTy, protocol, /*allowMissing=*/false);
if (inheritedConformance.hasUnavailableConformance())
inheritedConformance = ProtocolConformanceRef::forInvalid();
if (inheritedConformance)
return superclassDecl;
} while ((superclassDecl = superclassDecl->getSuperclassDecl()));
Expand Down
22 changes: 21 additions & 1 deletion lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6179,8 +6179,16 @@ ProtocolConformance *swift::deriveImplicitSendableConformance(

// Local function to form the implicit conformance.
auto formConformance = [&](const DeclAttribute *attrMakingUnavailable)
-> NormalProtocolConformance * {
-> ProtocolConformance * {
DeclContext *conformanceDC = nominal;

// FIXME: @_nonSendable should be a builtin extension macro. This behavior
// of explanding the unavailable conformance during implicit Sendable
// derivation means that clients can unknowingly ignore unavailable Sendable
// Sendable conformances from the original module added via @_nonSendable
// because they are not expanded if an explicit conformance is found via
// conformance lookup. So, if a retroactive, unchecked Sendable conformance
// is written, no redundant conformance warning is emitted.
if (attrMakingUnavailable) {
// Conformance availability is currently tied to the declaring extension.
// FIXME: This is a hack--we should give conformances real availability.
Expand Down Expand Up @@ -6208,6 +6216,18 @@ ProtocolConformance *swift::deriveImplicitSendableConformance(
file->getOrCreateSynthesizedFile().addTopLevelDecl(extension);

conformanceDC = extension;

// Let the conformance lookup table register the conformance
// from the extension. Otherwise, we'll end up with redundant
// conformances between the explicit conformance from the extension
// and the conformance synthesized below.
SmallVector<ProtocolConformance *, 2> conformances;
nominal->lookupConformance(proto, conformances);
for (auto conformance : conformances) {
if (conformance->getDeclContext() == conformanceDC) {
return conformance;
}
}
}

auto conformance = ctx.getNormalConformance(
Expand Down
60 changes: 48 additions & 12 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6256,20 +6256,56 @@ void TypeChecker::checkConformancesInContext(IterableDeclContext *idc) {
// protocol, just warn; we'll pick up the original conformance.
auto existingModule = diag.ExistingDC->getParentModule();
auto extendedNominal = diag.ExistingDC->getSelfNominalTypeDecl();
if (existingModule != dc->getParentModule() &&
(existingModule->getName() ==
extendedNominal->getParentModule()->getName() ||
auto definingModule = extendedNominal->getParentModule()->getName();
bool conformanceInOrigModule =
(existingModule->getName() == definingModule ||
existingModule == diag.Protocol->getParentModule() ||
existingModule->getName().is("CoreGraphics"))) {
existingModule->getName().is("CoreGraphics"));

// Redundant Sendable conformances are always warnings.
auto knownProtocol = diag.Protocol->getKnownProtocolKind();
bool isSendable = knownProtocol == KnownProtocolKind::Sendable;
// Try to find an inherited Sendable conformance if there is one.
if (isSendable && !SendableConformance) {
SmallVector<ProtocolConformance *, 2> conformances;
nominal->lookupConformance(diag.Protocol, conformances);
for (auto conformance : conformances) {
if (isa<InheritedProtocolConformance>(conformance))
SendableConformance = conformance;
}
}

if ((existingModule != dc->getParentModule() && conformanceInOrigModule) ||
isSendable) {
// Warn about the conformance.
auto diagID = differentlyConditional
? diag::redundant_conformance_adhoc_conditional
: diag::redundant_conformance_adhoc;
Context.Diags.diagnose(diag.Loc, diagID, dc->getDeclaredInterfaceType(),
diag.Protocol->getName(),
existingModule->getName() ==
extendedNominal->getParentModule()->getName(),
existingModule->getName());
if (isSendable && SendableConformance &&
isa<InheritedProtocolConformance>(SendableConformance)) {
// Allow re-stated unchecked conformances to Sendable in subclasses
// as long as the inherited conformance isn't unavailable.
auto *conformance = SendableConformance->getRootConformance();
auto *decl = conformance->getDeclContext()->getAsDecl();
if (!AvailableAttr::isUnavailable(decl)) {
continue;
}

Context.Diags.diagnose(diag.Loc, diag::unavailable_conformance,
nominal->getDeclaredInterfaceType(),
diag.Protocol->getName());
} else if (existingModule == dc->getParentModule()) {
Context.Diags.diagnose(diag.Loc, diag::redundant_conformance,
nominal->getDeclaredInterfaceType(),
diag.Protocol->getName())
.limitBehavior(DiagnosticBehavior::Warning);
} else {
auto diagID = differentlyConditional
? diag::redundant_conformance_adhoc_conditional
: diag::redundant_conformance_adhoc;
Context.Diags.diagnose(diag.Loc, diagID, dc->getDeclaredInterfaceType(),
diag.Protocol->getName(),
existingModule->getName() ==
extendedNominal->getParentModule()->getName(),
existingModule->getName());
}

// Complain about any declarations in this extension whose names match
// a requirement in that protocol.
Expand Down
7 changes: 7 additions & 0 deletions test/Concurrency/Inputs/SendableConformances.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

public class NonSendableClass {}

@available(*, unavailable)
extension NonSendableClass: @unchecked Sendable {}

public struct SendableStruct: Sendable {}
19 changes: 19 additions & 0 deletions test/Concurrency/redundant_sendable_conformance.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/SendableConformances.swiftmodule -module-name SendableConformances %S/Inputs/SendableConformances.swift

// RUN: %target-swift-frontend -typecheck %s -verify -swift-version 6 -I %t

// REQUIRES: concurrency

import SendableConformances

typealias RequireSendable<T: Sendable> = T

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

typealias CheckNonSendableClass = RequireSendable<NonSendableClass>
// expected-error@-1 {{conformance of 'NonSendableClass' to 'Sendable' is unavailable}}
Comment thread
hborla marked this conversation as resolved.
Outdated

extension SendableStruct: @retroactive @unchecked Sendable {}
// expected-warning@-1 {{conformance of 'SendableStruct' to protocol 'Sendable' was already stated in the type's module 'SendableConformances'}}
11 changes: 7 additions & 4 deletions test/Concurrency/sendable_checking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,21 +102,24 @@ public actor MyActor: MyProto {
}

// Make sure the generic signature doesn't minimize away Sendable requirements.
@_nonSendable class NSClass { }
class NSClass { }

@available(*, unavailable)
extension NSClass: @unchecked Sendable {} // expected-note {{conformance of 'NSClass' to 'Sendable' has been explicitly marked unavailable here}}

struct WrapClass<T: NSClass> {
var t: T
}

extension WrapClass: Sendable where T: Sendable { }

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

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


Expand Down
2 changes: 1 addition & 1 deletion test/Concurrency/sendable_conformance_checking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ extension SendableExtSub: @unchecked Sendable {}

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

@available(SwiftStdlib 5.1, *)
actor MyActor {
Expand Down