Skip to content

Commit cc75d5e

Browse files
committed
Allow @preconcurrency to suppress an implied Sendable conformance
When a retroactive conformance to a protocol that inherits from `Sendable` introduces an implicit `Sendable` conformance, allow `@preconcurrency` to suppress the diagnostic about the `Sendable` conformance being introduced outside of the original source file. Fixes rdar://125080066.
1 parent e4dac75 commit cc75d5e

File tree

5 files changed

+34
-8
lines changed

5 files changed

+34
-8
lines changed

include/swift/Sema/Concurrency.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ bool diagnoseNonSendableFromDeinit(
5252
/// the given nominal type from the given declaration context.
5353
std::optional<DiagnosticBehavior>
5454
getConcurrencyDiagnosticBehaviorLimit(NominalTypeDecl *nominal,
55-
const DeclContext *fromDC);
55+
const DeclContext *fromDC,
56+
bool ignoreExplicitConformance = false);
5657

5758
} // namespace swift
5859

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -836,7 +836,8 @@ DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
836836

837837
std::optional<DiagnosticBehavior>
838838
swift::getConcurrencyDiagnosticBehaviorLimit(NominalTypeDecl *nominal,
839-
const DeclContext *fromDC) {
839+
const DeclContext *fromDC,
840+
bool ignoreExplicitConformance) {
840841
ModuleDecl *importedModule = nullptr;
841842
if (nominal->getAttrs().hasAttribute<PreconcurrencyAttr>()) {
842843
// If the declaration itself has the @preconcurrency attribute,
@@ -859,7 +860,8 @@ swift::getConcurrencyDiagnosticBehaviorLimit(NominalTypeDecl *nominal,
859860

860861
// When the type is explicitly non-Sendable, @preconcurrency imports
861862
// downgrade the diagnostic to a warning in Swift 6.
862-
if (hasExplicitSendableConformance(nominal))
863+
if (!ignoreExplicitConformance &&
864+
hasExplicitSendableConformance(nominal))
863865
return DiagnosticBehavior::Warning;
864866

865867
// When the type is implicitly non-Sendable, `@preconcurrency` suppresses
@@ -870,12 +872,15 @@ swift::getConcurrencyDiagnosticBehaviorLimit(NominalTypeDecl *nominal,
870872
}
871873

872874
std::optional<DiagnosticBehavior>
873-
SendableCheckContext::preconcurrencyBehavior(Decl *decl) const {
875+
SendableCheckContext::preconcurrencyBehavior(
876+
Decl *decl,
877+
bool ignoreExplicitConformance) const {
874878
if (!decl)
875879
return std::nullopt;
876880

877881
if (auto *nominal = dyn_cast<NominalTypeDecl>(decl)) {
878-
return getConcurrencyDiagnosticBehaviorLimit(nominal, fromDC);
882+
return getConcurrencyDiagnosticBehaviorLimit(nominal, fromDC,
883+
ignoreExplicitConformance);
879884
}
880885

881886
return std::nullopt;
@@ -5847,8 +5852,16 @@ bool swift::checkSendableConformance(
58475852

58485853
// Sendable can only be used in the same source file.
58495854
auto conformanceDecl = conformanceDC->getAsDecl();
5850-
auto behavior = SendableCheckContext(conformanceDC, check)
5851-
.defaultDiagnosticBehavior();
5855+
SendableCheckContext checkContext(conformanceDC, check);
5856+
DiagnosticBehavior behavior = checkContext.defaultDiagnosticBehavior();
5857+
if (conformance->getSourceKind() == ConformanceEntryKind::Implied &&
5858+
conformance->getProtocol()->isSpecificProtocol(
5859+
KnownProtocolKind::Sendable)) {
5860+
if (auto optBehavior = checkContext.preconcurrencyBehavior(
5861+
nominal, /*ignoreExplicitConformance=*/true))
5862+
behavior = *optBehavior;
5863+
}
5864+
58525865
if (conformanceDC->getOutermostParentSourceFile() &&
58535866
conformanceDC->getOutermostParentSourceFile() !=
58545867
nominal->getOutermostParentSourceFile()) {

lib/Sema/TypeCheckConcurrency.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,9 @@ struct SendableCheckContext {
391391
/// type in this context.
392392
DiagnosticBehavior diagnosticBehavior(NominalTypeDecl *nominal) const;
393393

394-
std::optional<DiagnosticBehavior> preconcurrencyBehavior(Decl *decl) const;
394+
std::optional<DiagnosticBehavior> preconcurrencyBehavior(
395+
Decl *decl,
396+
bool ignoreExplicitConformance = false) const;
395397

396398
/// Whether we are in an explicit conformance to Sendable.
397399
bool isExplicitSendableConformance() const;

test/Concurrency/Inputs/NonStrictModule.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,8 @@ public protocol NonStrictProtocol {
1010
func send(_ body: @Sendable () -> Void)
1111
func dontSend(_ body: () -> Void)
1212
}
13+
14+
open class NonStrictClass2 { }
15+
open class NonStrictClass3 { }
16+
17+
public protocol MySendableProto: Sendable {}

test/Concurrency/predates_concurrency_import.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,8 @@ struct HasStatics {
6363
// expected-warning@-2{{static property 'ss' is not concurrency-safe because non-'Sendable' type 'StrictStruct' may have shared mutable state}}
6464
// expected-note@-3{{isolate 'ss' to a global actor, or conform 'StrictStruct' to 'Sendable'}}
6565
}
66+
67+
extension NonStrictClass2: @retroactive MySendableProto { }
68+
69+
extension NonStrictClass3: @retroactive Sendable { }
70+
// expected-warning@-1{{conformance to 'Sendable' must occur in the same source file as class 'NonStrictClass3'; use '@unchecked Sendable' for retroactive conformance}}

0 commit comments

Comments
 (0)