Skip to content

Commit 0d39a8d

Browse files
committed
Limit inference of Sendable for public, frozen types relying on preconcurrency
When inferring Sendable for a public, frozen type, that Sendable conformance becomes part of the contract. Therefore, don't infer this conformance when any of instance storage is implicitly non-Sendable. Fixes rdar://88652324.
1 parent a7c698c commit 0d39a8d

File tree

3 files changed

+69
-23
lines changed

3 files changed

+69
-23
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,7 @@ bool SendableCheckContext::isExplicitSendableConformance() const {
748748

749749
case SendableCheck::ImpliedByStandardProtocol:
750750
case SendableCheck::Implicit:
751+
case SendableCheck::ImplicitForExternallyVisible:
751752
return false;
752753
}
753754
}
@@ -861,7 +862,7 @@ DiagnosticBehavior SendableCheckContext::diagnosticBehavior(
861862
// enclosing inferred types non-Sendable.
862863
if (defaultBehavior == DiagnosticBehavior::Ignore &&
863864
nominal->getParentSourceFile() &&
864-
conformanceCheck && *conformanceCheck == SendableCheck::Implicit)
865+
conformanceCheck && isImplicitSendableCheck(*conformanceCheck))
865866
return DiagnosticBehavior::Warning;
866867

867868
return defaultBehavior;
@@ -4053,7 +4054,7 @@ static bool checkSendableInstanceStorage(
40534054
bool operator()(VarDecl *property, Type propertyType) {
40544055
// Classes with mutable properties are not Sendable.
40554056
if (property->supportsMutation() && isa<ClassDecl>(nominal)) {
4056-
if (check == SendableCheck::Implicit) {
4057+
if (isImplicitSendableCheck(check)) {
40574058
invalid = true;
40584059
return true;
40594060
}
@@ -4074,8 +4075,14 @@ static bool checkSendableInstanceStorage(
40744075
diagnoseNonSendableTypes(
40754076
propertyType, SendableCheckContext(dc, check), property->getLoc(),
40764077
[&](Type type, DiagnosticBehavior behavior) {
4077-
if (check == SendableCheck::Implicit) {
4078-
// If we are to ignore this diagnose, just continue.
4078+
if (isImplicitSendableCheck(check)) {
4079+
// If this is for an externally-visible conformance, fail.
4080+
if (check == SendableCheck::ImplicitForExternallyVisible) {
4081+
invalid = true;
4082+
return true;
4083+
}
4084+
4085+
// If we are to ignore this diagnostic, just continue.
40794086
if (behavior == DiagnosticBehavior::Ignore)
40804087
return false;
40814088

@@ -4093,7 +4100,7 @@ static bool checkSendableInstanceStorage(
40934100

40944101
if (invalid) {
40954102
// For implicit checks, bail out early if anything failed.
4096-
if (check == SendableCheck::Implicit)
4103+
if (isImplicitSendableCheck(check))
40974104
return true;
40984105
}
40994106

@@ -4105,8 +4112,14 @@ static bool checkSendableInstanceStorage(
41054112
diagnoseNonSendableTypes(
41064113
elementType, SendableCheckContext(dc, check), element->getLoc(),
41074114
[&](Type type, DiagnosticBehavior behavior) {
4108-
if (check == SendableCheck::Implicit) {
4109-
// If we are to ignore this diagnose, just continue.
4115+
if (isImplicitSendableCheck(check)) {
4116+
// If this is for an externally-visible conformance, fail.
4117+
if (check == SendableCheck::ImplicitForExternallyVisible) {
4118+
invalid = true;
4119+
return true;
4120+
}
4121+
4122+
// If we are to ignore this diagnostic, just continue.
41104123
if (behavior == DiagnosticBehavior::Ignore)
41114124
return false;
41124125

@@ -4124,7 +4137,7 @@ static bool checkSendableInstanceStorage(
41244137

41254138
if (invalid) {
41264139
// For implicit checks, bail out early if anything failed.
4127-
if (check == SendableCheck::Implicit)
4140+
if (isImplicitSendableCheck(check))
41284141
return true;
41294142
}
41304143

@@ -4352,21 +4365,27 @@ ProtocolConformance *GetImplicitSendableRequest::evaluate(
43524365
if (!isa<StructDecl>(nominal) && !isa<EnumDecl>(nominal))
43534366
return nullptr;
43544367

4355-
// Public, non-frozen structs and enums defined in Swift don't get implicit
4356-
// Sendable conformances.
4357-
if (!nominal->getASTContext().LangOpts.EnableInferPublicSendable &&
4358-
nominal->getFormalAccessScope(
4359-
/*useDC=*/nullptr,
4360-
/*treatUsableFromInlineAsPublic=*/true).isPublic() &&
4361-
!(nominal->hasClangNode() ||
4362-
nominal->getAttrs().hasAttribute<FixedLayoutAttr>() ||
4363-
nominal->getAttrs().hasAttribute<FrozenAttr>())) {
4368+
SendableCheck check;
4369+
4370+
// Okay to infer Sendable conformance for non-public types or when
4371+
// specifically requested.
4372+
if (nominal->getASTContext().LangOpts.EnableInferPublicSendable ||
4373+
!nominal->getFormalAccessScope(
4374+
/*useDC=*/nullptr, /*treatUsableFromInlineAsPublic=*/true)
4375+
.isPublic()) {
4376+
check = SendableCheck::Implicit;
4377+
} else if (nominal->hasClangNode() ||
4378+
nominal->getAttrs().hasAttribute<FixedLayoutAttr>() ||
4379+
nominal->getAttrs().hasAttribute<FrozenAttr>()) {
4380+
// @_frozen public types can also infer Sendable, but be more careful here.
4381+
check = SendableCheck::ImplicitForExternallyVisible;
4382+
} else {
4383+
// No inference.
43644384
return nullptr;
43654385
}
43664386

43674387
// Check the instance storage for Sendable conformance.
4368-
if (checkSendableInstanceStorage(
4369-
nominal, nominal, SendableCheck::Implicit))
4388+
if (checkSendableInstanceStorage(nominal, nominal, check))
43704389
return nullptr;
43714390

43724391
return formConformance(nullptr);

lib/Sema/TypeCheckConcurrency.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,25 @@ enum class SendableCheck {
267267

268268
/// Implicit conformance to Sendable.
269269
Implicit,
270+
271+
/// Implicit conformance to Sendable that would be externally-visible, i.e.,
272+
/// for a public @_frozen type.
273+
ImplicitForExternallyVisible,
270274
};
271275

276+
/// Whether this sendable check is implicit.
277+
static inline bool isImplicitSendableCheck(SendableCheck check) {
278+
switch (check) {
279+
case SendableCheck::Explicit:
280+
case SendableCheck::ImpliedByStandardProtocol:
281+
return false;
282+
283+
case SendableCheck::Implicit:
284+
case SendableCheck::ImplicitForExternallyVisible:
285+
return true;
286+
}
287+
}
288+
272289
/// Describes the context in which a \c Sendable check occurs.
273290
struct SendableCheckContext {
274291
const DeclContext * const fromDC;

test/ModuleInterface/actor_isolation.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
// RUN: %empty-directory(%t)
2-
// RUN: %target-swift-frontend -emit-module -o %t/Test.swiftmodule -emit-module-interface-path %t/Test.swiftinterface -module-name Test -enable-experimental-concurrency %s
2+
// RUN: %target-swift-frontend -emit-module -o %t/Preconcurrency.swiftmodule -module-name Preconcurrency %S/Inputs/preconcurrency.swift
3+
4+
// RUN: %target-swift-frontend -emit-module -o %t/Test.swiftmodule -emit-module-interface-path %t/Test.swiftinterface -module-name Test -enable-experimental-concurrency -I %t %s
35
// RUN: %FileCheck %s < %t/Test.swiftinterface
46
// RUN: %FileCheck %s -check-prefix SYNTHESIZED < %t/Test.swiftinterface
5-
// RUN: %target-swift-frontend -typecheck-module-from-interface -module-name Test %t/Test.swiftinterface
7+
// RUN: %target-swift-frontend -typecheck-module-from-interface -module-name Test %t/Test.swiftinterface -I %t
68

7-
// RUN: %target-swift-frontend -emit-module -o /dev/null -merge-modules %t/Test.swiftmodule -disable-objc-attr-requires-foundation-module -emit-module-interface-path %t/TestFromModule.swiftinterface -module-name Test -enable-experimental-concurrency
9+
// RUN: %target-swift-frontend -emit-module -o /dev/null -merge-modules %t/Test.swiftmodule -disable-objc-attr-requires-foundation-module -emit-module-interface-path %t/TestFromModule.swiftinterface -module-name Test -enable-experimental-concurrency -I %t
810
// RUN: %FileCheck %s < %t/TestFromModule.swiftinterface
9-
// RUN: %target-swift-frontend -typecheck-module-from-interface -module-name Test %t/TestFromModule.swiftinterface
11+
// RUN: %target-swift-frontend -typecheck-module-from-interface -module-name Test %t/TestFromModule.swiftinterface -I %t
1012

1113
// REQUIRES: concurrency
14+
import Preconcurrency
1215

1316
// CHECK: public actor SomeActor
1417

@@ -97,6 +100,13 @@ public class C8 : P2 {
97100
public func method() {}
98101
}
99102

103+
// CHECK-NOT: StructWithImplicitlyNonSendable{{.*}}Sendable
104+
@available(SwiftStdlib 5.1, *)
105+
@_frozen
106+
public struct StructWithImplicitlyNonSendable {
107+
var ns: NotSendable? = nil
108+
}
109+
100110
// FIXME: Work around a bug where module printing depends on the "synthesized"
101111
// bit in conformances which is not serialized and not present in the textual
102112
// form.

0 commit comments

Comments
 (0)