Skip to content

Commit 84eaeb7

Browse files
committed
[Concurrency] Diagnose non-Sendable 'self' arguments crossing actor isolation
boundaries in member reference expressions.
1 parent 1a010dd commit 84eaeb7

File tree

9 files changed

+56
-28
lines changed

9 files changed

+56
-28
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -999,7 +999,8 @@ bool swift::diagnoseNonSendableTypes(
999999
}
10001000

10011001
bool swift::diagnoseNonSendableTypesInReference(
1002-
ConcreteDeclRef declRef, const DeclContext *fromDC, SourceLoc refLoc,
1002+
Expr *base, ConcreteDeclRef declRef,
1003+
const DeclContext *fromDC, SourceLoc refLoc,
10031004
SendableCheckReason refKind, llvm::Optional<ActorIsolation> knownIsolation,
10041005
FunctionCheckKind funcCheckKind, SourceLoc diagnoseLoc) {
10051006
// Retrieve the actor isolation to use in diagnostics.
@@ -1010,6 +1011,17 @@ bool swift::diagnoseNonSendableTypesInReference(
10101011
return swift::getActorIsolation(declRef.getDecl());
10111012
};
10121013

1014+
// Check the 'self' argument.
1015+
if (base) {
1016+
if (diagnoseNonSendableTypes(
1017+
base->getType(),
1018+
fromDC, base->getStartLoc(),
1019+
diag::non_sendable_param_type,
1020+
(unsigned)refKind, declRef.getDecl(),
1021+
getActorIsolation()))
1022+
return true;
1023+
}
1024+
10131025
// For functions, check the parameter and result types.
10141026
SubstitutionMap subs = declRef.getSubstitutions();
10151027
if (auto function = dyn_cast<AbstractFunctionDecl>(declRef.getDecl())) {
@@ -2625,7 +2637,6 @@ namespace {
26252637
FoundAsync, // successfully marked an implicitly-async operation
26262638
NotFound, // fail: no valid implicitly-async operation was found
26272639
SyncContext, // fail: a valid implicitly-async op, but in sync context
2628-
NotSendable, // fail: valid op and context, but not Sendable
26292640
NotDistributed, // fail: non-distributed declaration in distributed actor
26302641
};
26312642

@@ -2744,16 +2755,6 @@ namespace {
27442755
}
27452756
}
27462757

2747-
if (result == AsyncMarkingResult::FoundAsync) {
2748-
// Check for non-sendable types.
2749-
bool problemFound =
2750-
diagnoseNonSendableTypesInReference(
2751-
concDeclRef, getDeclContext(), declLoc,
2752-
SendableCheckReason::SynchronousAsAsync);
2753-
if (problemFound)
2754-
result = AsyncMarkingResult::NotSendable;
2755-
}
2756-
27572758
return result;
27582759
}
27592760

@@ -3155,7 +3156,7 @@ namespace {
31553156
return true;
31563157

31573158
return diagnoseNonSendableTypesInReference(
3158-
declRef, getDeclContext(), loc,
3159+
base, declRef, getDeclContext(), loc,
31593160
SendableCheckReason::ExitingActor,
31603161
result.isolation);
31613162

@@ -3190,7 +3191,7 @@ namespace {
31903191
// Sendable checking and we're done.
31913192
if (!result.options) {
31923193
return diagnoseNonSendableTypesInReference(
3193-
declRef, getDeclContext(), loc,
3194+
base, declRef, getDeclContext(), loc,
31943195
SendableCheckReason::CrossActor);
31953196
}
31963197

@@ -3203,11 +3204,11 @@ namespace {
32033204
loc, declRef, context, result.isolation, isDistributed);
32043205
switch (implicitAsyncResult) {
32053206
case AsyncMarkingResult::FoundAsync:
3206-
// Success! We're done.
3207-
return false;
3207+
return diagnoseNonSendableTypesInReference(
3208+
base, declRef, getDeclContext(), loc,
3209+
SendableCheckReason::SynchronousAsAsync);
32083210

32093211
case AsyncMarkingResult::NotDistributed:
3210-
case AsyncMarkingResult::NotSendable:
32113212
// Failed, but diagnostics have already been emitted.
32123213
return true;
32133214

@@ -4426,12 +4427,14 @@ void swift::checkOverrideActorIsolation(ValueDecl *value) {
44264427
case OverrideIsolationResult::Sendable:
44274428
// Check that the results of the overriding method are sendable
44284429
diagnoseNonSendableTypesInReference(
4430+
/*base=*/nullptr,
44294431
getDeclRefInContext(value), value->getInnermostDeclContext(),
44304432
value->getLoc(), SendableCheckReason::Override,
44314433
getActorIsolation(value), FunctionCheckKind::Results);
44324434

44334435
// Check that the parameters of the overridden method are sendable
44344436
diagnoseNonSendableTypesInReference(
4437+
/*base=*/nullptr,
44354438
getDeclRefInContext(overridden), overridden->getInnermostDeclContext(),
44364439
overridden->getLoc(), SendableCheckReason::Override,
44374440
getActorIsolation(value), FunctionCheckKind::Params,

lib/Sema/TypeCheckConcurrency.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ enum class FunctionCheckKind {
265265
/// concurrency domain, whether in/out of an actor or in/or of a concurrent
266266
/// function or closure.
267267
///
268+
/// \param base The base expression of the reference, which must be 'Sendable'
269+
/// in order to cross actor isolation boundaries.
270+
///
268271
/// \param declRef The declaration being referenced from another concurrency
269272
/// domain, including the substitutions so that (e.g.) we can consider the
270273
/// specific types at the use site.
@@ -286,7 +289,8 @@ enum class FunctionCheckKind {
286289
///
287290
/// \returns true if an problem was detected, false otherwise.
288291
bool diagnoseNonSendableTypesInReference(
289-
ConcreteDeclRef declRef, const DeclContext *fromDC, SourceLoc refLoc,
292+
Expr *base, ConcreteDeclRef declRef,
293+
const DeclContext *fromDC, SourceLoc refLoc,
290294
SendableCheckReason refKind,
291295
llvm::Optional<ActorIsolation> knownIsolation = llvm::None,
292296
FunctionCheckKind funcCheckKind = FunctionCheckKind::ParamsResults,

lib/Sema/TypeCheckDeclObjC.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ static bool checkObjCActorIsolation(const ValueDecl *VD, ObjCReason Reason) {
470470

471471
// FIXME: Substitution map?
472472
diagnoseNonSendableTypesInReference(
473-
const_cast<ValueDecl *>(VD), VD->getDeclContext(),
473+
/*base=*/nullptr, const_cast<ValueDecl *>(VD), VD->getDeclContext(),
474474
VD->getLoc(), SendableCheckReason::ObjC);
475475
return false;
476476

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3041,7 +3041,8 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement,
30413041

30423042
case ActorReferenceResult::ExitsActorToNonisolated:
30433043
diagnoseNonSendableTypesInReference(
3044-
getDeclRefInContext(witness), DC, loc, SendableCheckReason::Conformance);
3044+
/*base=*/nullptr, getDeclRefInContext(witness),
3045+
DC, loc, SendableCheckReason::Conformance);
30453046
return llvm::None;
30463047
case ActorReferenceResult::EntersActor:
30473048
// Handled below.
@@ -3124,13 +3125,14 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement,
31243125

31253126
// Check that the results of the witnessing method are sendable
31263127
diagnoseNonSendableTypesInReference(
3127-
getDeclRefInContext(witness), DC, loc, SendableCheckReason::Conformance,
3128+
/*base=*/nullptr, getDeclRefInContext(witness),
3129+
DC, loc, SendableCheckReason::Conformance,
31283130
getActorIsolation(witness), FunctionCheckKind::Results);
31293131

31303132
// If this requirement is a function, check that its parameters are Sendable as well
31313133
if (isa<AbstractFunctionDecl>(requirement)) {
31323134
diagnoseNonSendableTypesInReference(
3133-
getDeclRefInContext(requirement),
3135+
/*base=*/nullptr, getDeclRefInContext(requirement),
31343136
requirement->getInnermostDeclContext(), requirement->getLoc(),
31353137
SendableCheckReason::Conformance, getActorIsolation(witness),
31363138
FunctionCheckKind::Params, loc);

test/Concurrency/Inputs/other_global_actor_inference.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ public dynamic func dynamicOnMainActor() { }
55

66
// property wrapper + main actor
77
@propertyWrapper
8-
public struct IntWrapper {
8+
public struct IntWrapper: Sendable {
99

1010
public init(wrappedValue: Int) {
1111
self.wrappedValue = wrappedValue

test/Concurrency/actor_isolation.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,11 @@ func checkIsolationValueType(_ formance: InferredFromConformance,
147147
_ = await ext.point // expected-warning {{non-sendable type 'Point' in implicitly asynchronous access to main actor-isolated property 'point' cannot cross actor boundary}}
148148
_ = await formance.counter
149149
_ = await anno.point // expected-warning {{non-sendable type 'Point' in implicitly asynchronous access to global actor 'SomeGlobalActor'-isolated property 'point' cannot cross actor boundary}}
150-
_ = anno.counter
150+
// expected-warning@-1 {{non-sendable type 'NoGlobalActorValueType' passed in implicitly asynchronous call to global actor 'SomeGlobalActor'-isolated property 'point' cannot cross actor boundary}}
151+
_ = anno.counter // expected-warning {{non-sendable type 'NoGlobalActorValueType' passed in call to main actor-isolated property 'counter' cannot cross actor boundary}}
151152

152153
// these will always need an await
153-
_ = await (formance as MainCounter).counter
154+
_ = await (formance as MainCounter).counter // expected-warning {{non-sendable type 'any MainCounter' passed in implicitly asynchronous call to main actor-isolated property 'counter' cannot cross actor boundary}}
154155
_ = await ext[1]
155156
_ = await formance.ticker
156157
_ = await ext.polygon // expected-warning {{non-sendable type '[Point]' in implicitly asynchronous access to main actor-isolated property 'polygon' cannot cross actor boundary}}
@@ -159,6 +160,7 @@ func checkIsolationValueType(_ formance: InferredFromConformance,
159160
}
160161

161162
// check for instance members that do not need global-actor protection
163+
// expected-note@+1 2 {{consider making struct 'NoGlobalActorValueType' conform to the 'Sendable' protocol}}
162164
struct NoGlobalActorValueType {
163165
@SomeGlobalActor var point: Point // expected-warning {{stored property 'point' within struct cannot have a global actor; this is an error in Swift 6}}
164166

test/Concurrency/actor_isolation_swift6.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func checkIsolationValueType(_ formance: InferredFromConformance,
6060

6161
// these do need await, regardless of reference or value type
6262
_ = await (formance as any MainCounter).counter
63+
// expected-warning@-1 {{non-sendable type 'any MainCounter' passed in implicitly asynchronous call to main actor-isolated property 'counter' cannot cross actor boundary}}
6364
_ = await ext[1]
6465
_ = await formance.ticker
6566
_ = await ext.polygon

test/Concurrency/sendable_checking.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ func testConversionsAndSendable(a: MyActor, s: any Sendable, f: @Sendable () ->
233233

234234
@available(SwiftStdlib 5.1, *)
235235
final class NonSendable {
236-
// expected-note@-1 3 {{class 'NonSendable' does not conform to the 'Sendable' protocol}}
236+
// expected-note@-1 6 {{class 'NonSendable' does not conform to the 'Sendable' protocol}}
237237
var value = ""
238238

239239
@MainActor
@@ -247,12 +247,24 @@ final class NonSendable {
247247

248248
await self.update()
249249
// expected-warning@-1 {{passing argument of non-sendable type 'NonSendable' into main actor-isolated context may introduce data races}}
250+
251+
_ = await x
252+
// expected-warning@-1 {{non-sendable type 'NonSendable' passed in implicitly asynchronous call to main actor-isolated property 'x' cannot cross actor boundary}}
253+
254+
_ = await self.x
255+
// expected-warning@-1 {{non-sendable type 'NonSendable' passed in implicitly asynchronous call to main actor-isolated property 'x' cannot cross actor boundary}}
250256
}
257+
258+
@MainActor
259+
var x: Int { 0 }
251260
}
252261

253262
@available(SwiftStdlib 5.1, *)
254263
func testNonSendableBaseArg() async {
255264
let t = NonSendable()
256265
await t.update()
257266
// expected-warning@-1 {{passing argument of non-sendable type 'NonSendable' into main actor-isolated context may introduce data races}}
267+
268+
_ = await t.x
269+
// expected-warning@-1 {{non-sendable type 'NonSendable' passed in implicitly asynchronous call to main actor-isolated property 'x' cannot cross actor boundary}}
258270
}

validation-test/Sema/SwiftUI/rdar76252310.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class Visibility: ObservableObject { // expected-note 2{{class 'Visibility' does
99
@Published var yes = false // some nonsense
1010
}
1111

12-
struct CoffeeTrackerView: View { // expected-note{{consider making struct 'CoffeeTrackerView' conform to the 'Sendable' protocol}}
12+
struct CoffeeTrackerView: View { // expected-note 4{{consider making struct 'CoffeeTrackerView' conform to the 'Sendable' protocol}}
1313
@ObservedObject var showDrinkList: Visibility = Visibility()
1414

1515
var storage: Visibility = Visibility()
@@ -39,15 +39,19 @@ func fromConcurrencyAware() async {
3939
// expected-warning@+1 {{non-sendable type 'CoffeeTrackerView' returned by call to main actor-isolated function cannot cross actor boundary}}
4040
let view = CoffeeTrackerView()
4141

42+
// expected-warning@+4 {{non-sendable type 'CoffeeTrackerView' passed in implicitly asynchronous call to main actor-isolated property 'body' cannot cross actor boundary}}
4243
// expected-note@+3 {{property access is 'async'}}
4344
// expected-warning@+2 {{non-sendable type 'some View' in implicitly asynchronous access to main actor-isolated property 'body' cannot cross actor boundary}}
4445
// expected-error@+1 {{expression is 'async' but is not marked with 'await'}}
4546
_ = view.body
4647

48+
// expected-warning@+4 {{non-sendable type 'CoffeeTrackerView' passed in implicitly asynchronous call to main actor-isolated property 'showDrinkList' cannot cross actor boundary}}
4749
// expected-note@+3 {{property access is 'async'}}
4850
// expected-warning@+2 {{non-sendable type 'Visibility' in implicitly asynchronous access to main actor-isolated property 'showDrinkList' cannot cross actor boundary}}
4951
// expected-error@+1 {{expression is 'async' but is not marked with 'await'}}
5052
_ = view.showDrinkList
5153

52-
_ = await view.storage // expected-warning {{non-sendable type 'Visibility' in implicitly asynchronous access to main actor-isolated property 'storage' cannot cross actor boundary}}
54+
// expected-warning@+2 {{non-sendable type 'CoffeeTrackerView' passed in implicitly asynchronous call to main actor-isolated property 'storage' cannot cross actor boundary}}
55+
// expected-warning@+1 {{non-sendable type 'Visibility' in implicitly asynchronous access to main actor-isolated property 'storage' cannot cross actor boundary}}
56+
_ = await view.storage
5357
}

0 commit comments

Comments
 (0)