Skip to content

Commit 44c5f9f

Browse files
authored
Merge pull request #71866 from hborla/restate-unchecked-sendable
[Concurrency] Warn if implied `@unchecked Sendable` conformances are not re-stated.
2 parents bf2a77e + eba4545 commit 44c5f9f

File tree

5 files changed

+68
-8
lines changed

5 files changed

+68
-8
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5640,6 +5640,9 @@ ERROR(sendable_raw_storage,none,
56405640

56415641
WARNING(unchecked_conformance_not_special,none,
56425642
"@unchecked conformance to %0 has no meaning", (Type))
5643+
WARNING(restate_unchecked_sendable,none,
5644+
"class %0 must restate inherited '@unchecked Sendable' conformance",
5645+
(DeclName))
56435646
ERROR(unchecked_not_inheritance_clause,none,
56445647
"'unchecked' attribute only applies in inheritance clauses", ())
56455648
ERROR(unchecked_not_existential,none,

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5596,6 +5596,55 @@ bool swift::checkSendableConformance(
55965596
return false;
55975597
}
55985598

5599+
bool isUnchecked = false;
5600+
if (auto *normal = conformance->getRootNormalConformance())
5601+
isUnchecked = normal->isUnchecked();
5602+
5603+
if (isUnchecked) {
5604+
// Warn if inferred or inherited '@unchecked Sendable' is not restated.
5605+
// Beyond that, '@unchecked Sendable' requires no further checking.
5606+
5607+
if (!isa<InheritedProtocolConformance>(conformance))
5608+
return false;
5609+
5610+
auto statesUnchecked =
5611+
[](InheritedTypes inheritedTypes, DeclContext *dc) -> bool {
5612+
for (auto i : inheritedTypes.getIndices()) {
5613+
auto inheritedType =
5614+
TypeResolution::forInterface(
5615+
dc,
5616+
TypeResolverContext::Inherited,
5617+
/*unboundTyOpener*/ nullptr,
5618+
/*placeholderHandler*/ nullptr,
5619+
/*packElementOpener*/ nullptr)
5620+
.resolveType(inheritedTypes.getTypeRepr(i));
5621+
5622+
if (!inheritedType || inheritedType->hasError())
5623+
continue;
5624+
5625+
if (inheritedType->getKnownProtocol() != KnownProtocolKind::Sendable)
5626+
continue;
5627+
5628+
if (inheritedTypes.getEntry(i).isUnchecked())
5629+
return true;
5630+
}
5631+
5632+
return false;
5633+
};
5634+
5635+
if (statesUnchecked(nominal->getInherited(), nominal))
5636+
return false;
5637+
5638+
for (auto *extension : nominal->getExtensions()) {
5639+
if (statesUnchecked(extension->getInherited(), extension))
5640+
return false;
5641+
}
5642+
5643+
nominal->diagnose(diag::restate_unchecked_sendable,
5644+
nominal->getName());
5645+
return false;
5646+
}
5647+
55995648
auto classDecl = dyn_cast<ClassDecl>(nominal);
56005649
if (classDecl) {
56015650
// Actors implicitly conform to Sendable and protect their state.

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6024,7 +6024,7 @@ void TypeChecker::checkConformancesInContext(IterableDeclContext *idc) {
60246024
MultiConformanceChecker groupChecker(Context);
60256025

60266026
ProtocolConformance *SendableConformance = nullptr;
6027-
bool sendableConformanceIsUnchecked = false;
6027+
bool hasDeprecatedUnsafeSendable = false;
60286028
bool sendableConformancePreconcurrency = false;
60296029
bool anyInvalid = false;
60306030
for (auto conformance : conformances) {
@@ -6056,12 +6056,8 @@ void TypeChecker::checkConformancesInContext(IterableDeclContext *idc) {
60566056
SendableConformance = conformance;
60576057

60586058
if (auto normal = conformance->getRootNormalConformance()) {
6059-
if (normal->isUnchecked())
6060-
sendableConformanceIsUnchecked = true;
6061-
else if (isImpliedByConformancePredatingConcurrency(normal))
6059+
if (isImpliedByConformancePredatingConcurrency(normal))
60626060
sendableConformancePreconcurrency = true;
6063-
else if (isa<InheritedProtocolConformance>(conformance))
6064-
sendableConformanceIsUnchecked = true;
60656061
}
60666062
} else if (proto->isSpecificProtocol(KnownProtocolKind::DistributedActor)) {
60676063
if (auto classDecl = dyn_cast<ClassDecl>(nominal)) {
@@ -6105,7 +6101,7 @@ void TypeChecker::checkConformancesInContext(IterableDeclContext *idc) {
61056101
}
61066102
} else if (proto->isSpecificProtocol(
61076103
KnownProtocolKind::UnsafeSendable)) {
6108-
sendableConformanceIsUnchecked = true;
6104+
hasDeprecatedUnsafeSendable = true;
61096105
} else if (proto->isSpecificProtocol(KnownProtocolKind::Executor)) {
61106106
tryDiagnoseExecutorConformance(Context, nominal, proto);
61116107
} else if (NoncopyableGenerics
@@ -6123,7 +6119,7 @@ void TypeChecker::checkConformancesInContext(IterableDeclContext *idc) {
61236119
}
61246120

61256121
// Check constraints of Sendable.
6126-
if (SendableConformance && !sendableConformanceIsUnchecked) {
6122+
if (!hasDeprecatedUnsafeSendable && SendableConformance) {
61276123
SendableCheck check = SendableCheck::Explicit;
61286124
if (sendableConformancePreconcurrency)
61296125
check = SendableCheck::ImpliedByStandardProtocol;

test/Concurrency/concurrent_value_checking.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,10 +336,21 @@ class C5: @unchecked Sendable {
336336
var x: Int = 0 // okay
337337
}
338338

339+
// expected-warning@+1 {{class 'C6' must restate inherited '@unchecked Sendable' conformance}}
339340
class C6: C5 {
340341
var y: Int = 0 // still okay, it's unsafe
341342
}
342343

344+
class C6_Restated: C5, @unchecked Sendable {
345+
var y: Int = 0 // still okay, it's unsafe
346+
}
347+
348+
class C6_Restated_Extension: C5 {
349+
var y: Int = 0 // still okay, it's unsafe
350+
}
351+
352+
extension C6_Restated_Extension: @unchecked Sendable {}
353+
343354
final class C7<T>: Sendable { }
344355

345356
class C9: Sendable { } // expected-warning{{non-final class 'C9' cannot conform to 'Sendable'; use '@unchecked Sendable'}}

test/decl/class/actor/basic.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class MyActorSubclass1: MyActor { }
88
// expected-error@-1{{actor types do not support inheritance}}
99
// expected-error@-2{{type 'MyActorSubclass1' cannot conform to the 'Actor' protocol}}
1010
// expected-error@-3{{non-actor type 'MyActorSubclass1' cannot conform to the 'AnyActor' protocol}}
11+
// expected-warning@-4 {{non-final class 'MyActorSubclass1' cannot conform to 'Sendable'; use '@unchecked Sendable'; this is an error in Swift 6}}
1112

1213
actor MyActorSubclass2: MyActor { } // expected-error{{actor types do not support inheritance}}
1314

0 commit comments

Comments
 (0)