Skip to content

Commit 1aaba1f

Browse files
committed
Don't check sendability of witnesses for @preconcurrency conformances
When a witness is part of a `@preconcurrency` conformance, suppress Sendable checking for that witness because we assume that the caller is correctly invoking this witness from within the right isolation domain. This property is dynamically checked. Fixes #74057.
1 parent 62558b0 commit 1aaba1f

File tree

3 files changed

+52
-38
lines changed

3 files changed

+52
-38
lines changed

lib/Sema/TypeCheckProtocol.cpp

+33-28
Original file line numberDiff line numberDiff line change
@@ -3090,7 +3090,8 @@ static bool hasExplicitGlobalActorAttr(ValueDecl *decl) {
30903090

30913091
std::optional<ActorIsolation>
30923092
ConformanceChecker::checkActorIsolation(ValueDecl *requirement,
3093-
ValueDecl *witness) {
3093+
ValueDecl *witness,
3094+
bool &usesPreconcurrency) {
30943095

30953096
// Determine the isolation of the requirement itself.
30963097
auto requirementIsolation = getActorIsolation(requirement);
@@ -3136,9 +3137,16 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement,
31363137
return std::nullopt;
31373138

31383139
case ActorReferenceResult::ExitsActorToNonisolated:
3139-
diagnoseNonSendableTypesInReference(
3140-
/*base=*/nullptr, getDeclRefInContext(witness),
3141-
DC, loc, SendableCheckReason::Conformance);
3140+
if (!isPreconcurrency) {
3141+
diagnoseNonSendableTypesInReference(
3142+
/*base=*/nullptr, getDeclRefInContext(witness),
3143+
DC, loc, SendableCheckReason::Conformance);
3144+
} else {
3145+
// We depended on @preconcurrency since we were exiting an isolation
3146+
// domain.
3147+
usesPreconcurrency = true;
3148+
}
3149+
31423150
return std::nullopt;
31433151
case ActorReferenceResult::EntersActor:
31443152
// Handled below.
@@ -3220,19 +3228,24 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement,
32203228
witness->getAttrs().hasAttribute<NonisolatedAttr>())
32213229
return std::nullopt;
32223230

3223-
// Check that the results of the witnessing method are sendable
3224-
diagnoseNonSendableTypesInReference(
3225-
/*base=*/nullptr, getDeclRefInContext(witness),
3226-
DC, loc, SendableCheckReason::Conformance,
3227-
getActorIsolation(witness), FunctionCheckKind::Results);
3228-
3229-
// If this requirement is a function, check that its parameters are Sendable as well
3230-
if (isa<AbstractFunctionDecl>(requirement)) {
3231+
if (!isPreconcurrency) {
3232+
// Check that the results of the witnessing method are sendable
32313233
diagnoseNonSendableTypesInReference(
3232-
/*base=*/nullptr, getDeclRefInContext(requirement),
3233-
requirement->getInnermostDeclContext(), requirement->getLoc(),
3234-
SendableCheckReason::Conformance, getActorIsolation(witness),
3235-
FunctionCheckKind::Params, loc);
3234+
/*base=*/nullptr, getDeclRefInContext(witness),
3235+
DC, loc, SendableCheckReason::Conformance,
3236+
getActorIsolation(witness), FunctionCheckKind::Results);
3237+
3238+
// If this requirement is a function, check that its parameters are Sendable as well
3239+
if (isa<AbstractFunctionDecl>(requirement)) {
3240+
diagnoseNonSendableTypesInReference(
3241+
/*base=*/nullptr, getDeclRefInContext(requirement),
3242+
requirement->getInnermostDeclContext(), requirement->getLoc(),
3243+
SendableCheckReason::Conformance, getActorIsolation(witness),
3244+
FunctionCheckKind::Params, loc);
3245+
}
3246+
} else {
3247+
// We depended on @preconcurrency to suppress Sendable checking.
3248+
usesPreconcurrency = true;
32363249
}
32373250

32383251
// If the witness is accessible across actors, we don't need to consider it
@@ -4955,7 +4968,7 @@ hasInvalidTypeInConformanceContext(const ValueDecl *requirement,
49554968
}
49564969

49574970
void ConformanceChecker::resolveValueWitnesses() {
4958-
bool usesPreconcurrencyConformance = false;
4971+
bool usesPreconcurrency = false;
49594972

49604973
for (auto *requirement : Proto->getProtocolRequirements()) {
49614974
// Associated type requirements handled elsewhere.
@@ -4974,16 +4987,8 @@ void ConformanceChecker::resolveValueWitnesses() {
49744987

49754988
// Check actor isolation. If we need to enter into the actor's
49764989
// isolation within the witness thunk, record that.
4977-
if (auto enteringIsolation = checkActorIsolation(requirement, witness)) {
4978-
// Only @preconcurrency conformances allow entering isolation
4979-
// when neither requirement nor witness are async by adding a
4980-
// runtime precondition that witness is always called on the
4981-
// expected executor.
4982-
if (Conformance->isPreconcurrency()) {
4983-
usesPreconcurrencyConformance =
4984-
!isAsyncDecl(requirement) && !isAsyncDecl(witness);
4985-
}
4986-
4990+
if (auto enteringIsolation = checkActorIsolation(requirement, witness,
4991+
usesPreconcurrency)) {
49874992
Conformance->overrideWitness(
49884993
requirement,
49894994
Conformance->getWitnessUncached(requirement)
@@ -5134,7 +5139,7 @@ void ConformanceChecker::resolveValueWitnesses() {
51345139
}
51355140
}
51365141

5137-
if (Conformance->isPreconcurrency() && !usesPreconcurrencyConformance) {
5142+
if (Conformance->isPreconcurrency() && !usesPreconcurrency) {
51385143
auto diag = DC->getASTContext().Diags.diagnose(
51395144
Conformance->getLoc(), diag::preconcurrency_conformance_not_used,
51405145
Proto->getDeclaredInterfaceType());

lib/Sema/TypeCheckProtocol.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,15 @@ class ConformanceChecker : public WitnessChecker {
130130

131131
/// Check that the witness and requirement have compatible actor contexts.
132132
///
133+
/// \param usesPreconcurrency Will be set true if the conformance is
134+
/// @preconcurrency and we made use of that fact.
135+
///
133136
/// \returns the isolation that needs to be enforced to invoke the witness
134137
/// from the requirement, used when entering an actor-isolated synchronous
135138
/// witness from an asynchronous requirement.
136139
std::optional<ActorIsolation> checkActorIsolation(ValueDecl *requirement,
137-
ValueDecl *witness);
140+
ValueDecl *witness,
141+
bool &usesPreconcurrency);
138142

139143
/// Enforce restrictions on non-final classes witnessing requirements
140144
/// involving the protocol 'Self' type.

test/Concurrency/preconcurrency_conformances.swift

+14-9
Original file line numberDiff line numberDiff line change
@@ -35,28 +35,36 @@ extension TestPreconcurrencyAttr : @preconcurrency Q { // Ok
3535
}
3636

3737
class NonSendable {}
38-
// expected-note@-1 6 {{class 'NonSendable' does not conform to the 'Sendable' protocol}}
3938

4039
protocol TestSendability {
4140
var x: NonSendable { get }
4241
func test(_: NonSendable?) -> [NonSendable]
4342
}
4443

45-
// Make sure that preconcurrency conformances don't suppress Sendable diagnostics
44+
// Make sure that preconcurrency conformances suppress Sendable diagnostics,
45+
// because @preconcurrency assumes that the witness will always be called
46+
// from within the same isolation domain (with a dynamic check).
4647
@MainActor
4748
struct Value : @preconcurrency TestSendability {
4849
var x: NonSendable { NonSendable() }
49-
// expected-warning@-1 {{non-sendable type 'NonSendable' in conformance of main actor-isolated property 'x' to protocol requirement cannot cross actor boundary}}
50-
// expected-note@-2 2 {{property declared here}}
50+
// expected-note@-1 2 {{property declared here}}
5151

52-
// expected-warning@+2 {{non-sendable type '[NonSendable]' returned by main actor-isolated instance method 'test' satisfying protocol requirement cannot cross actor boundary}}
53-
// expected-warning@+1 {{non-sendable type 'NonSendable?' in parameter of the protocol requirement satisfied by main actor-isolated instance method 'test' cannot cross actor boundary}}
5452
func test(_: NonSendable?) -> [NonSendable] {
5553
// expected-note@-1 2 {{calls to instance method 'test' from outside of its actor context are implicitly asynchronous}}
5654
[]
5755
}
5856
}
5957

58+
// Make sure we don't spuriously say the @preconcurrency is unnecessary.
59+
@MainActor
60+
struct OtherValue : @preconcurrency TestSendability {
61+
var x: NonSendable { NonSendable() }
62+
63+
nonisolated func test(_: NonSendable?) -> [NonSendable] {
64+
[]
65+
}
66+
}
67+
6068
// Make sure that references to actor isolated witness is diagnosed
6169

6270
// expected-note@+1 2 {{add '@MainActor' to make global function 'test(value:)' part of global actor 'MainActor'}}
@@ -73,10 +81,7 @@ actor MyActor {
7381

7482
extension MyActor : @preconcurrency TestSendability {
7583
var x: NonSendable { NonSendable() }
76-
// expected-warning@-1 {{non-sendable type 'NonSendable' in conformance of actor-isolated property 'x' to protocol requirement cannot cross actor boundary}}
7784

78-
// expected-warning@+2 {{non-sendable type '[NonSendable]' returned by actor-isolated instance method 'test' satisfying protocol requirement cannot cross actor boundary}}
79-
// expected-warning@+1 {{non-sendable type 'NonSendable?' in parameter of the protocol requirement satisfied by actor-isolated instance method 'test' cannot cross actor boundary}}
8085
func test(_: NonSendable?) -> [NonSendable] {
8186
[]
8287
}

0 commit comments

Comments
 (0)