Skip to content

Commit 95b18d3

Browse files
committed
Downgrade sendability errors to warnings when they involve non-Sendable metatypes
The introduction of non-Sendable metatypes in Swift 6.2 (via SE-0470) will break some existing Swift 6 code. Downgrade concurrency errors involving non-Sendable metatypes to warnings until some future language mode to ease the transition.
1 parent 3ed1d6c commit 95b18d3

File tree

5 files changed

+73
-24
lines changed

5 files changed

+73
-24
lines changed

lib/AST/Type.cpp

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5006,11 +5006,15 @@ StringRef swift::getNameForParamSpecifier(ParamSpecifier specifier) {
50065006
llvm_unreachable("bad ParamSpecifier");
50075007
}
50085008

5009-
std::optional<DiagnosticBehavior>
5010-
TypeBase::getConcurrencyDiagnosticBehaviorLimit(DeclContext *declCtx) const {
5011-
auto *self = const_cast<TypeBase *>(this);
5009+
static std::optional<DiagnosticBehavior>
5010+
getConcurrencyDiagnosticBehaviorLimitRec(
5011+
Type type, DeclContext *declCtx,
5012+
llvm::SmallPtrSetImpl<NominalTypeDecl *> &visited) {
5013+
if (auto *nomDecl = type->getNominalOrBoundGenericNominal()) {
5014+
// If we have already seen this type, treat it as having no limit.
5015+
if (!visited.insert(nomDecl).second)
5016+
return std::nullopt;
50125017

5013-
if (auto *nomDecl = self->getNominalOrBoundGenericNominal()) {
50145018
// First try to just grab the exact concurrency diagnostic behavior.
50155019
if (auto result =
50165020
swift::getConcurrencyDiagnosticBehaviorLimit(nomDecl, declCtx)) {
@@ -5021,11 +5025,12 @@ TypeBase::getConcurrencyDiagnosticBehaviorLimit(DeclContext *declCtx) const {
50215025
// merging our fields if we have a struct.
50225026
if (auto *structDecl = dyn_cast<StructDecl>(nomDecl)) {
50235027
std::optional<DiagnosticBehavior> diagnosticBehavior;
5024-
auto substMap = self->getContextSubstitutionMap();
5028+
auto substMap = type->getContextSubstitutionMap();
50255029
for (auto storedProperty : structDecl->getStoredProperties()) {
50265030
auto lhs = diagnosticBehavior.value_or(DiagnosticBehavior::Unspecified);
50275031
auto astType = storedProperty->getInterfaceType().subst(substMap);
5028-
auto rhs = astType->getConcurrencyDiagnosticBehaviorLimit(declCtx);
5032+
auto rhs = getConcurrencyDiagnosticBehaviorLimitRec(astType, declCtx,
5033+
visited);
50295034
auto result = lhs.merge(rhs.value_or(DiagnosticBehavior::Unspecified));
50305035
if (result != DiagnosticBehavior::Unspecified)
50315036
diagnosticBehavior = result;
@@ -5036,21 +5041,36 @@ TypeBase::getConcurrencyDiagnosticBehaviorLimit(DeclContext *declCtx) const {
50365041

50375042
// When attempting to determine the diagnostic behavior limit of a tuple, just
50385043
// merge for each of the elements.
5039-
if (auto *tupleType = self->getAs<TupleType>()) {
5044+
if (auto *tupleType = type->getAs<TupleType>()) {
50405045
std::optional<DiagnosticBehavior> diagnosticBehavior;
50415046
for (auto tupleType : tupleType->getElements()) {
50425047
auto lhs = diagnosticBehavior.value_or(DiagnosticBehavior::Unspecified);
50435048

50445049
auto type = tupleType.getType()->getCanonicalType();
5045-
auto rhs = type->getConcurrencyDiagnosticBehaviorLimit(declCtx);
5050+
auto rhs = getConcurrencyDiagnosticBehaviorLimitRec(type, declCtx,
5051+
visited);
50465052
auto result = lhs.merge(rhs.value_or(DiagnosticBehavior::Unspecified));
50475053
if (result != DiagnosticBehavior::Unspecified)
50485054
diagnosticBehavior = result;
50495055
}
50505056
return diagnosticBehavior;
50515057
}
50525058

5053-
return {};
5059+
// Metatypes that aren't Sendable were introduced in Swift 6.2, so downgrade
5060+
// them to warnings prior to Swift 7.
5061+
if (type->is<AnyMetatypeType>()) {
5062+
if (!type->getASTContext().LangOpts.isSwiftVersionAtLeast(7))
5063+
return DiagnosticBehavior::Warning;
5064+
}
5065+
5066+
return std::nullopt;
5067+
}
5068+
5069+
std::optional<DiagnosticBehavior>
5070+
TypeBase::getConcurrencyDiagnosticBehaviorLimit(DeclContext *declCtx) const {
5071+
auto *self = const_cast<TypeBase *>(this);
5072+
llvm::SmallPtrSet<NominalTypeDecl *, 16> visited;
5073+
return getConcurrencyDiagnosticBehaviorLimitRec(Type(self), declCtx, visited);
50545074
}
50555075

50565076
GenericTypeParamKind

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,12 @@ SendableCheckContext::preconcurrencyBehavior(
878878
return std::nullopt;
879879
}
880880

881+
std::optional<DiagnosticBehavior>
882+
SendableCheckContext::preconcurrencyBehavior(Type type) const {
883+
return type->getConcurrencyDiagnosticBehaviorLimit(
884+
const_cast<DeclContext *>(fromDC));
885+
}
886+
881887
static bool shouldDiagnosePreconcurrencyImports(SourceFile &sf) {
882888
switch (sf.Kind) {
883889
case SourceFileKind::Interface:
@@ -6671,8 +6677,7 @@ static bool checkSendableInstanceStorage(
66716677
propertyType, context,
66726678
/*inDerivedConformance*/Type(), property->getLoc(),
66736679
[&](Type type, DiagnosticBehavior behavior) {
6674-
auto preconcurrency =
6675-
context.preconcurrencyBehavior(type->getAnyNominal());
6680+
auto preconcurrency = context.preconcurrencyBehavior(type);
66766681
if (isImplicitSendableCheck(check)) {
66776682
// If this is for an externally-visible conformance, fail.
66786683
if (check == SendableCheck::ImplicitForExternallyVisible) {

lib/Sema/TypeCheckConcurrency.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -407,10 +407,18 @@ struct SendableCheckContext {
407407
/// type in this context.
408408
DiagnosticBehavior diagnosticBehavior(NominalTypeDecl *nominal) const;
409409

410+
/// Determine the preconcurrency behavior when referencing the given
411+
/// declaration from a type. This only has an effect when the declaration
412+
/// is a nominal type.
410413
std::optional<DiagnosticBehavior> preconcurrencyBehavior(
411414
Decl *decl,
412415
bool ignoreExplicitConformance = false) const;
413416

417+
/// Determine the preconcurrency behavior when referencing the given
418+
/// non-Sendable type. This only has an effect when the declaration
419+
/// is a nominal or metatype type.
420+
std::optional<DiagnosticBehavior> preconcurrencyBehavior(Type type) const;
421+
414422
/// Whether to warn about a Sendable violation even in minimal checking.
415423
bool warnInMinimalChecking() const;
416424
};
@@ -460,8 +468,7 @@ bool diagnoseNonSendableTypes(
460468
[&](Type specificType, DiagnosticBehavior behavior) {
461469
// FIXME: Reconcile preconcurrency declaration vs preconcurrency
462470
// import behavior.
463-
auto preconcurrency =
464-
fromContext.preconcurrencyBehavior(specificType->getAnyNominal());
471+
auto preconcurrency = fromContext.preconcurrencyBehavior(specificType);
465472

466473
ctx.Diags.diagnose(diagnoseLoc, diag, type, diagArgs...)
467474
.limitBehaviorWithPreconcurrency(behavior,
@@ -497,8 +504,7 @@ bool diagnoseIfAnyNonSendableTypes(
497504
diagnoseNonSendableTypes(
498505
type, fromContext, derivedConformance, typeLoc,
499506
[&](Type specificType, DiagnosticBehavior behavior) {
500-
auto preconcurrency =
501-
fromContext.preconcurrencyBehavior(specificType->getAnyNominal());
507+
auto preconcurrency = fromContext.preconcurrencyBehavior(specificType);
502508

503509
if (behavior == DiagnosticBehavior::Ignore ||
504510
preconcurrency == DiagnosticBehavior::Ignore)

test/Concurrency/sendable_metatype.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ func acceptMetaOnMainActor<T>(_: T.Type) { }
1919
// -------------------------------------------------------------------------
2020
nonisolated func staticCallThroughMetaSmuggled<T: Q>(_: T.Type) {
2121
let x: Q.Type = T.self
22-
Task.detached { // expected-error{{risks causing data races}}
22+
Task.detached { // expected-warning{{risks causing data races}}
2323
x.g() // expected-note{{closure captures 'x' which is accessible to code in the current task}}
2424
}
2525
}
2626

2727
nonisolated func passMetaSmuggled<T: Q>(_: T.Type) {
2828
let x: Q.Type = T.self
29-
Task.detached { // expected-error{{risks causing data races}}
29+
Task.detached { // expected-warning{{risks causing data races}}
3030
acceptMeta(x) // expected-note{{closure captures 'x' which is accessible to code in the current task}}
3131
}
3232
}
@@ -79,7 +79,7 @@ nonisolated func passSendableToMainActorSmuggledAny<T: Sendable>(_: T.Type) asyn
7979
// -------------------------------------------------------------------------
8080
nonisolated func passMetaSmuggledAnyFromExistential(_ pqT: (P & Q).Type) {
8181
let x: P.Type = pqT
82-
Task.detached { // expected-error{{passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure}}
82+
Task.detached { // expected-warning{{passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure}}
8383
acceptMeta(x) // expected-note{{closure captures 'x' which is accessible to code in the current task}}
8484
}
8585
}

test/Concurrency/sendable_metatype_typecheck.swift

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ func testSendableExistential() {
2121
nonisolated func acceptMeta<T>(_: T.Type) { }
2222

2323
nonisolated func staticCallThroughMetaVal<T: Q>(_: T.Type) {
24-
let x = T.self // expected-error{{capture of non-sendable type 'T.Type' in an isolated closure}}
24+
let x = T.self // expected-warning{{capture of non-sendable type 'T.Type' in an isolated closure}}
2525
Task.detached {
26-
x.g() // expected-error{{capture of non-sendable type 'T.Type' in an isolated closure}}
26+
x.g() // expected-warning{{capture of non-sendable type 'T.Type' in an isolated closure}}
2727
}
2828
}
2929

@@ -35,21 +35,21 @@ nonisolated func captureThroughMetaValMoReqs<T>(_: T.Type) {
3535
}
3636

3737
nonisolated func passMetaVal<T: Q>(_: T.Type) {
38-
let x = T.self // expected-error{{capture of non-sendable type 'T.Type' in an isolated closure}}
38+
let x = T.self // expected-warning{{capture of non-sendable type 'T.Type' in an isolated closure}}
3939
Task.detached {
40-
acceptMeta(x) // expected-error{{capture of non-sendable type}}
40+
acceptMeta(x) // expected-warning{{capture of non-sendable type}}
4141
}
4242
}
4343

4444
nonisolated func staticCallThroughMeta<T: Q>(_: T.Type) {
4545
Task.detached {
46-
T.g() // expected-error{{capture of non-sendable type}}
46+
T.g() // expected-warning{{capture of non-sendable type}}
4747
}
4848
}
4949

5050
nonisolated func passMeta<T: Q>(_: T.Type) {
5151
Task.detached {
52-
acceptMeta(T.self) // expected-error{{capture of non-sendable type 'T.Type' in an isolated closure}}
52+
acceptMeta(T.self) // expected-warning{{capture of non-sendable type 'T.Type' in an isolated closure}}
5353
}
5454
}
5555

@@ -99,3 +99,21 @@ struct GenericThingy<Element> {
9999
let _: @Sendable (Element, Element) -> Bool = (>) // expected-error{{converting non-sendable function value to '@Sendable (Element, Element) -> Bool' may introduce data races}}
100100
}
101101
}
102+
103+
extension Int: Q {
104+
static func g() { }
105+
}
106+
107+
extension String: Q {
108+
static func g() { }
109+
}
110+
111+
class Holder: @unchecked Sendable {
112+
// expected-note@+3{{disable concurrency-safety checks if accesses are protected by an external synchronization mechanism}}
113+
// expected-note@+2{{add '@MainActor' to make static property 'globalExistentialThing' part of global actor 'MainActor'}}
114+
// expected-warning@+1{{static property 'globalExistentialThing' is not concurrency-safe because non-'Sendable' type 'Dictionary<Int, any Q.Type>' may have shared mutable state}}
115+
static let globalExistentialThing: Dictionary<Int, Q.Type> = [
116+
1: Int.self,
117+
2: String.self,
118+
]
119+
}

0 commit comments

Comments
 (0)