Skip to content

Commit 6390fcb

Browse files
authored
Merge pull request #59086 from DougGregor/se-0338-sendable
2 parents 0eafd08 + 882e1f8 commit 6390fcb

11 files changed

+181
-67
lines changed

CHANGELOG.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,39 @@ _**Note:** This is in reverse chronological order, so newer entries are added to
55

66
## Swift 5.7
77

8+
* [SE-0338][]:
9+
10+
Non-isolated async functions now always execute on the global concurrent pool,
11+
so calling a non-isolated async function from actor-isolated code will leave
12+
the actor. For example:
13+
14+
```swift
15+
class C { }
16+
17+
func f(_: C) async { /* always executes on the global concurrent pool */ }
18+
19+
actor A {
20+
func g(c: C) async {
21+
/* always executes on the actor */
22+
print("on the actor")
23+
24+
await f(c)
25+
}
26+
}
27+
```
28+
29+
Prior to this change, the call from `f` to `g` might have started execution of
30+
`g` on the actor, which could lead to actors being busy longer than strictly
31+
necessary. Now, the non-isolated async function will always hop to the global
32+
cooperative pool, not run on the actor. This can result in a behavior change
33+
for programs that assumed that a non-isolated async function called from a
34+
`@MainActor` context will be executed on the main actor, although such
35+
programs were already technically incorrect.
36+
37+
Additionally, when leaving an actor to execution on the global cooperative
38+
pool, `Sendable` checking will be performed, so the compiler will emit a
39+
diagnostic in the call to `f` if `c` is not of `Sendable` type.
40+
841
* [SE-0350][]:
942

1043
The standard library has a new `Regex<Output>` type.
@@ -9421,6 +9454,7 @@ Swift 1.0
94219454
[SE-0335]: <https://github.com/apple/swift-evolution/blob/main/proposals/0335-existential-any.md>
94229455
[SE-0336]: <https://github.com/apple/swift-evolution/blob/main/proposals/0336-distributed-actor-isolation.md>
94239456
[SE-0337]: <https://github.com/apple/swift-evolution/blob/main/proposals/0337-support-incremental-migration-to-concurrency-checking.md>
9457+
[SE-0338]: <https://github.com/apple/swift-evolution/blob/main/proposals/0338-clarify-execution-non-actor-async.md>
94249458
[SE-0340]: <https://github.com/apple/swift-evolution/blob/main/proposals/0340-swift-noasync.md>
94259459
[SE-0341]: <https://github.com/apple/swift-evolution/blob/main/proposals/0341-opaque-parameters.md>
94269460
[SE-0343]: <https://github.com/apple/swift-evolution/blob/main/proposals/0343-top-level-concurrency.md>

include/swift/AST/DiagnosticsSema.def

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4657,9 +4657,10 @@ ERROR(isolated_parameter_not_actor,none,
46574657

46584658
WARNING(non_sendable_param_type,none,
46594659
"non-sendable type %0 %select{passed in call to %4 %2 %3|"
4660+
"exiting %4 context in call to non-isolated %2 %3|"
46604661
"passed in implicitly asynchronous call to %4 %2 %3|"
4661-
"in parameter of %4 %2 %3 satisfying non-isolated protocol "
4662-
"requirement|"
4662+
"in parameter of %4 %2 %3 satisfying protocol requirement|"
4663+
"in parameter of %4 overriding %2 %3|"
46634664
"in parameter of %4 '@objc' %2 %3}1 cannot cross actor boundary",
46644665
(Type, unsigned, DescriptiveDeclKind, DeclName, ActorIsolation))
46654666
WARNING(non_sendable_call_param_type,none,
@@ -4668,8 +4669,10 @@ WARNING(non_sendable_call_param_type,none,
46684669
(Type, bool, ActorIsolation))
46694670
WARNING(non_sendable_result_type,none,
46704671
"non-sendable type %0 returned by %select{call to %4 %2 %3|"
4672+
"call from %4 context to non-isolated %2 %3|"
46714673
"implicitly asynchronous call to %4 %2 %3|"
4672-
"%4 %2 %3 satisfying non-isolated protocol requirement|"
4674+
"%4 %2 %3 satisfying protocol requirement|"
4675+
"%4 overriding %2 %3|"
46734676
"%4 '@objc' %2 %3}1 cannot cross actor boundary",
46744677
(Type, unsigned, DescriptiveDeclKind, DeclName, ActorIsolation))
46754678
WARNING(non_sendable_call_result_type,none,
@@ -4679,8 +4682,10 @@ WARNING(non_sendable_call_result_type,none,
46794682
WARNING(non_sendable_property_type,none,
46804683
"non-sendable type %0 in %select{"
46814684
"%select{asynchronous access to %5 %1 %2|"
4685+
"asynchronous access from %5 context to non-isolated %1 %2|"
46824686
"implicitly asynchronous access to %5 %1 %2|"
4683-
"conformance of %5 %1 %2 to non-isolated protocol requirement|"
4687+
"conformance of %5 %1 %2 to protocol requirement|"
4688+
"%5 overriding %1 %2|"
46844689
"%5 '@objc' %1 %2}4|captured local %1 %2}3 cannot "
46854690
"cross %select{actor|task}3 boundary",
46864691
(Type, DescriptiveDeclKind, DeclName, bool, unsigned, ActorIsolation))

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 64 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,16 @@ bool swift::diagnoseNonSendableTypes(
934934

935935
bool swift::diagnoseNonSendableTypesInReference(
936936
ConcreteDeclRef declRef, const DeclContext *fromDC, SourceLoc loc,
937-
SendableCheckReason reason) {
937+
SendableCheckReason reason, Optional<ActorIsolation> knownIsolation) {
938+
939+
// Retrieve the actor isolation to use in diagnostics.
940+
auto getActorIsolation = [&] {
941+
if (knownIsolation)
942+
return *knownIsolation;
943+
944+
return swift::getActorIsolation(declRef.getDecl());
945+
};
946+
938947
// For functions, check the parameter and result types.
939948
SubstitutionMap subs = declRef.getSubstitutions();
940949
if (auto function = dyn_cast<AbstractFunctionDecl>(declRef.getDecl())) {
@@ -943,7 +952,7 @@ bool swift::diagnoseNonSendableTypesInReference(
943952
if (diagnoseNonSendableTypes(
944953
paramType, fromDC, loc, diag::non_sendable_param_type,
945954
(unsigned)reason, function->getDescriptiveKind(),
946-
function->getName(), getActorIsolation(function)))
955+
function->getName(), getActorIsolation()))
947956
return true;
948957
}
949958

@@ -953,7 +962,7 @@ bool swift::diagnoseNonSendableTypesInReference(
953962
if (diagnoseNonSendableTypes(
954963
resultType, fromDC, loc, diag::non_sendable_result_type,
955964
(unsigned)reason, func->getDescriptiveKind(), func->getName(),
956-
getActorIsolation(func)))
965+
getActorIsolation()))
957966
return true;
958967
}
959968

@@ -970,7 +979,7 @@ bool swift::diagnoseNonSendableTypesInReference(
970979
var->getDescriptiveKind(), var->getName(),
971980
var->isLocalCapture(),
972981
(unsigned)reason,
973-
getActorIsolation(var)))
982+
getActorIsolation()))
974983
return true;
975984
}
976985

@@ -980,7 +989,7 @@ bool swift::diagnoseNonSendableTypesInReference(
980989
if (diagnoseNonSendableTypes(
981990
paramType, fromDC, loc, diag::non_sendable_param_type,
982991
(unsigned)reason, subscript->getDescriptiveKind(),
983-
subscript->getName(), getActorIsolation(subscript)))
992+
subscript->getName(), getActorIsolation()))
984993
return true;
985994
}
986995

@@ -989,7 +998,7 @@ bool swift::diagnoseNonSendableTypesInReference(
989998
if (diagnoseNonSendableTypes(
990999
resultType, fromDC, loc, diag::non_sendable_result_type,
9911000
(unsigned)reason, subscript->getDescriptiveKind(),
992-
subscript->getName(), getActorIsolation(subscript)))
1001+
subscript->getName(), getActorIsolation()))
9931002
return true;
9941003

9951004
return false;
@@ -2772,8 +2781,10 @@ namespace {
27722781
if (diagnoseReferenceToUnsafeGlobal(decl, loc))
27732782
return true;
27742783

2775-
// FIXME: SE-0338 would trigger Sendable checks here.
2776-
return false;
2784+
return diagnoseNonSendableTypesInReference(
2785+
declRef, getDeclContext(), loc,
2786+
SendableCheckReason::ExitingActor,
2787+
result.isolation);
27772788

27782789
case ActorReferenceResult::EntersActor:
27792790
// Handle all of the checking below.
@@ -3539,19 +3550,25 @@ static ActorIsolation getOverriddenIsolationFor(ValueDecl *value) {
35393550
return isolation.subst(subs);
35403551
}
35413552

3553+
static ConcreteDeclRef getDeclRefInContext(ValueDecl *value) {
3554+
auto declContext = value->getInnermostDeclContext();
3555+
if (auto genericEnv = declContext->getGenericEnvironmentOfContext()) {
3556+
return ConcreteDeclRef(
3557+
value, genericEnv->getForwardingSubstitutionMap());
3558+
}
3559+
3560+
return ConcreteDeclRef(value);
3561+
}
3562+
35423563
/// Generally speaking, the isolation of the decl that overrides
35433564
/// must match the overridden decl. But there are a number of exceptions,
35443565
/// e.g., the decl that overrides can be nonisolated.
35453566
/// \param isolation the isolation of the overriding declaration.
35463567
static OverrideIsolationResult validOverrideIsolation(
35473568
ValueDecl *value, ActorIsolation isolation,
35483569
ValueDecl *overridden, ActorIsolation overriddenIsolation) {
3549-
ConcreteDeclRef valueRef(value);
3570+
ConcreteDeclRef valueRef = getDeclRefInContext(value);
35503571
auto declContext = value->getInnermostDeclContext();
3551-
if (auto genericEnv = declContext->getGenericEnvironmentOfContext()) {
3552-
valueRef = ConcreteDeclRef(
3553-
value, genericEnv->getForwardingSubstitutionMap());
3554-
}
35553572

35563573
auto refResult = ActorReferenceResult::forReference(
35573574
valueRef, SourceLoc(), declContext, None, None,
@@ -3570,9 +3587,7 @@ static OverrideIsolationResult validOverrideIsolation(
35703587
if (isAsyncDecl(overridden) ||
35713588
isAccessibleAcrossActors(
35723589
overridden, refResult.isolation, declContext)) {
3573-
// FIXME: Perform Sendable checking here because we're entering an
3574-
// actor.
3575-
return OverrideIsolationResult::Allowed;
3590+
return OverrideIsolationResult::Sendable;
35763591
}
35773592

35783593
// If the overridden declaration is from Objective-C with no actor
@@ -3948,7 +3963,9 @@ void swift::checkOverrideActorIsolation(ValueDecl *value) {
39483963
return;
39493964

39503965
case OverrideIsolationResult::Sendable:
3951-
// FIXME: Do the Sendable check.
3966+
diagnoseNonSendableTypesInReference(
3967+
getDeclRefInContext(value), value->getInnermostDeclContext(),
3968+
value->getLoc(), SendableCheckReason::Override);
39523969
return;
39533970

39543971
case OverrideIsolationResult::Disallowed:
@@ -4886,9 +4903,8 @@ bool swift::isThrowsDecl(ConcreteDeclRef declRef) {
48864903
return false;
48874904
}
48884905

4889-
bool swift::isAccessibleAcrossActors(
4890-
ValueDecl *value, const ActorIsolation &isolation,
4891-
const DeclContext *fromDC, Optional<ReferencedActor> actorInstance) {
4906+
/// Determine whether a reference to this value isn't actually a value.
4907+
static bool isNonValueReference(const ValueDecl *value) {
48924908
switch (value->getKind()) {
48934909
case DeclKind::AssociatedType:
48944910
case DeclKind::Class:
@@ -4899,13 +4915,7 @@ bool swift::isAccessibleAcrossActors(
48994915
case DeclKind::Protocol:
49004916
case DeclKind::Struct:
49014917
case DeclKind::TypeAlias:
4902-
return true;
4903-
49044918
case DeclKind::EnumCase:
4905-
case DeclKind::EnumElement:
4906-
// Type-level entities are always accessible across actors.
4907-
return true;
4908-
49094919
case DeclKind::IfConfig:
49104920
case DeclKind::Import:
49114921
case DeclKind::InfixOperator:
@@ -4917,16 +4927,26 @@ bool swift::isAccessibleAcrossActors(
49174927
case DeclKind::PrecedenceGroup:
49184928
case DeclKind::PrefixOperator:
49194929
case DeclKind::TopLevelCode:
4920-
// Non-value entities are always accessible across actors.
4921-
return true;
4922-
49234930
case DeclKind::Destructor:
4924-
// Destructors are always accessible across actors.
49254931
return true;
49264932

4933+
case DeclKind::EnumElement:
49274934
case DeclKind::Constructor:
4928-
// Initializers are accessible across actors unless they are global-actor
4929-
// qualified.
4935+
case DeclKind::Param:
4936+
case DeclKind::Var:
4937+
case DeclKind::Accessor:
4938+
case DeclKind::Func:
4939+
case DeclKind::Subscript:
4940+
return false;
4941+
}
4942+
}
4943+
4944+
bool swift::isAccessibleAcrossActors(
4945+
ValueDecl *value, const ActorIsolation &isolation,
4946+
const DeclContext *fromDC, Optional<ReferencedActor> actorInstance) {
4947+
// Initializers and enum elements are accessible across actors unless they
4948+
// are global-actor qualified.
4949+
if (isa<ConstructorDecl>(value) || isa<EnumElementDecl>(value)) {
49304950
switch (isolation) {
49314951
case ActorIsolation::ActorInstance:
49324952
case ActorIsolation::Independent:
@@ -4937,19 +4957,15 @@ bool swift::isAccessibleAcrossActors(
49374957
case ActorIsolation::GlobalActor:
49384958
return false;
49394959
}
4960+
}
49404961

4941-
case DeclKind::Param:
4942-
case DeclKind::Var:
4943-
// 'let' declarations are immutable, so some of them can be accessed across
4944-
// actors.
4945-
return varIsSafeAcrossActors(
4946-
fromDC->getParentModule(), cast<VarDecl>(value), isolation);
4947-
4948-
case DeclKind::Accessor:
4949-
case DeclKind::Func:
4950-
case DeclKind::Subscript:
4951-
return false;
4962+
// 'let' declarations are immutable, so some of them can be accessed across
4963+
// actors.
4964+
if (auto var = dyn_cast<VarDecl>(value)) {
4965+
return varIsSafeAcrossActors(fromDC->getParentModule(), var, isolation);
49524966
}
4967+
4968+
return false;
49534969
}
49544970

49554971
ActorReferenceResult ActorReferenceResult::forSameConcurrencyDomain(
@@ -4998,6 +5014,11 @@ ActorReferenceResult ActorReferenceResult::forReference(
49985014
declIsolation = declIsolation.subst(declRef.getSubstitutions());
49995015
}
50005016

5017+
// If the entity we are referencing is not a value, we're in thesame
5018+
// concurrency domain.
5019+
if (isNonValueReference(declRef.getDecl()))
5020+
return forSameConcurrencyDomain(declIsolation);
5021+
50015022
// Compute the isolation of the context, if not provided.
50025023
ActorIsolation contextIsolation = ActorIsolation::forUnspecified();
50035024
if (knownContextIsolation) {

lib/Sema/TypeCheckConcurrency.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ enum class SendableCheckReason {
7575
/// A reference to an actor from outside that actor.
7676
CrossActor,
7777

78+
/// Exiting an actor to non-isolated async code.
79+
ExitingActor,
80+
7881
/// A synchronous operation that was "promoted" to an asynchronous one
7982
/// because it was out of the actor's domain.
8083
SynchronousAsAsync,
@@ -83,6 +86,9 @@ enum class SendableCheckReason {
8386
/// actor isolation.
8487
Conformance,
8588

89+
/// An override of a function.
90+
Override,
91+
8692
/// The declaration is being exposed to Objective-C.
8793
ObjC,
8894
};
@@ -268,7 +274,8 @@ struct ActorReferenceResult {
268274
/// \returns true if an problem was detected, false otherwise.
269275
bool diagnoseNonSendableTypesInReference(
270276
ConcreteDeclRef declRef, const DeclContext *fromDC, SourceLoc loc,
271-
SendableCheckReason refKind);
277+
SendableCheckReason refKind,
278+
Optional<ActorIsolation> knownIsolation = None);
272279

273280
/// Produce a diagnostic for a missing conformance to Sendable.
274281
void diagnoseMissingSendableConformance(

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2955,6 +2955,10 @@ bool ConformanceChecker::checkActorIsolation(
29552955
requirementIsolation = requirementIsolation.subst(subs);
29562956
}
29572957

2958+
SourceLoc loc = witness->getLoc();
2959+
if (loc.isInvalid())
2960+
loc = Conformance->getLoc();
2961+
29582962
auto refResult = ActorReferenceResult::forReference(
29592963
getConcreteWitness(), witness->getLoc(), DC, None, None,
29602964
None, requirementIsolation);
@@ -2972,7 +2976,8 @@ bool ConformanceChecker::checkActorIsolation(
29722976
return false;
29732977

29742978
case ActorReferenceResult::ExitsActorToNonisolated:
2975-
// FIXME: SE-0338 would diagnose this.
2979+
diagnoseNonSendableTypesInReference(
2980+
getConcreteWitness(), DC, loc, SendableCheckReason::Conformance);
29762981
return false;
29772982

29782983
case ActorReferenceResult::EntersActor:
@@ -3016,10 +3021,6 @@ bool ConformanceChecker::checkActorIsolation(
30163021

30173022
// If we aren't missing anything, do a Sendable check and move on.
30183023
if (!missingOptions) {
3019-
SourceLoc loc = witness->getLoc();
3020-
if (loc.isInvalid())
3021-
loc = Conformance->getLoc();
3022-
30233024
// FIXME: Disable Sendable checking when the witness is an initializer
30243025
// that is explicitly marked nonisolated.
30253026
if (isa<ConstructorDecl>(witness) &&

test/Concurrency/concurrent_value_checking.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ protocol AsyncProto {
223223
}
224224

225225
extension A1: AsyncProto {
226-
func asyncMethod(_: NotConcurrent) async { } // expected-warning{{non-sendable type 'NotConcurrent' in parameter of actor-isolated instance method 'asyncMethod' satisfying non-isolated protocol requirement cannot cross actor boundary}}
226+
func asyncMethod(_: NotConcurrent) async { } // expected-warning{{non-sendable type 'NotConcurrent' in parameter of actor-isolated instance method 'asyncMethod' satisfying protocol requirement cannot cross actor boundary}}
227227
}
228228

229229
protocol MainActorProto {
@@ -232,7 +232,7 @@ protocol MainActorProto {
232232

233233
class SomeClass: MainActorProto {
234234
@SomeGlobalActor
235-
func asyncMainMethod(_: NotConcurrent) async { } // expected-warning{{non-sendable type 'NotConcurrent' in parameter of global actor 'SomeGlobalActor'-isolated instance method 'asyncMainMethod' satisfying non-isolated protocol requirement cannot cross actor boundary}}
235+
func asyncMainMethod(_: NotConcurrent) async { } // expected-warning{{non-sendable type 'NotConcurrent' in parameter of global actor 'SomeGlobalActor'-isolated instance method 'asyncMainMethod' satisfying protocol requirement cannot cross actor boundary}}
236236
}
237237

238238
// ----------------------------------------------------------------------

0 commit comments

Comments
 (0)