Skip to content

Commit b046efc

Browse files
committed
[Concurrency] Downgrade errors when isolated member is referenced from a preconcurrency context
Such references used to be downgraded until Swift 6 but since the context is `@preconcurrency` it should be possible for API authors to introduce concurrency annotations such as `@Sendable` without breaking clients even when they are compiled in Swift 6 mode. Resolves: rdar://157061896
1 parent 45656ab commit b046efc

9 files changed

+96
-29
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "swift/Strings.h"
4141

4242
using namespace swift;
43+
using namespace swift::version;
4344

4445
static ActorIsolation getOverriddenIsolationFor(const ValueDecl *value);
4546

@@ -567,7 +568,7 @@ static bool varIsSafeAcrossActors(const ModuleDecl *fromModule, VarDecl *var,
567568
if (var->isGlobalStorage() && var->isLazilyInitializedGlobal()) {
568569
// Compiler versions <= 5.9 accepted this code, so downgrade to a
569570
// warning prior to Swift 6.
570-
options = ActorReferenceResult::Flags::Preconcurrency;
571+
options = ActorReferenceResult::Flags::CompatibilityDowngrade;
571572
return false;
572573
}
573574

@@ -588,7 +589,7 @@ static bool varIsSafeAcrossActors(const ModuleDecl *fromModule, VarDecl *var,
588589
// so downgrade async access errors in the effects checker to
589590
// warnings prior to Swift 6.
590591
if (accessWithinModule)
591-
options = ActorReferenceResult::Flags::Preconcurrency;
592+
options = ActorReferenceResult::Flags::CompatibilityDowngrade;
592593

593594
return false;
594595
}
@@ -4227,11 +4228,10 @@ namespace {
42274228

42284229
applyErrors[key].push_back(mismatch);
42294230
} else {
4230-
ctx.Diags.diagnose(
4231-
apply->getLoc(),
4232-
diagnostic.getID(),
4233-
diagnostic.getArgs())
4234-
.warnUntilSwiftVersionIf(preconcurrency, 6);
4231+
ctx.Diags
4232+
.diagnose(apply->getLoc(), diagnostic.getID(),
4233+
diagnostic.getArgs())
4234+
.limitBehaviorIf(preconcurrency, DiagnosticBehavior::Warning);
42354235

42364236
if (calleeDecl) {
42374237
auto calleeIsolation = getInferredActorIsolation(calleeDecl);
@@ -4575,9 +4575,10 @@ namespace {
45754575
break;
45764576
}
45774577

4578-
bool downgrade = isolation.isGlobalActor() ||
4579-
options.contains(
4580-
ActorReferenceResult::Flags::Preconcurrency);
4578+
bool downgrade =
4579+
isolation.isGlobalActor() ||
4580+
options.contains(
4581+
ActorReferenceResult::Flags::CompatibilityDowngrade);
45814582

45824583
ctx.Diags.diagnose(
45834584
component.getLoc(), diag::actor_isolated_keypath_component,
@@ -4767,6 +4768,10 @@ namespace {
47674768
// Does the reference originate from a @preconcurrency context?
47684769
bool preconcurrencyContext =
47694770
result.options.contains(ActorReferenceResult::Flags::Preconcurrency);
4771+
bool shouldDowngradeToWarning =
4772+
preconcurrencyContext ||
4773+
result.options.contains(
4774+
ActorReferenceResult::Flags::CompatibilityDowngrade);
47704775

47714776
Type derivedConformanceType;
47724777
DeclName requirementName;
@@ -4801,14 +4806,21 @@ namespace {
48014806
refErrors.insert(std::make_pair(keyPair, list));
48024807
}
48034808
} else {
4804-
ctx.Diags.diagnose(
4805-
loc, diag::actor_isolated_non_self_reference,
4806-
decl, useKind,
4807-
refKind + 1, refGlobalActor,
4808-
result.isolation)
4809-
.warnUntilSwiftVersionIf(preconcurrencyContext, 6);
4810-
maybeNoteMutatingMethodSuggestion(ctx, decl, loc, getDeclContext(), result.isolation,
4811-
kindOfUsage(decl, context).value_or(VarRefUseEnv::Read));
4809+
{
4810+
auto diagnostic = ctx.Diags.diagnose(
4811+
loc, diag::actor_isolated_non_self_reference, decl, useKind,
4812+
refKind + 1, refGlobalActor, result.isolation);
4813+
4814+
// For compatibility downgrades - the error is downgraded until
4815+
// Swift 6, for preconcurrency - always.
4816+
if (shouldDowngradeToWarning)
4817+
diagnostic.limitBehaviorWithPreconcurrency(
4818+
DiagnosticBehavior::Warning, preconcurrencyContext);
4819+
}
4820+
4821+
maybeNoteMutatingMethodSuggestion(
4822+
ctx, decl, loc, getDeclContext(), result.isolation,
4823+
kindOfUsage(decl, context).value_or(VarRefUseEnv::Read));
48124824

48134825
if (derivedConformanceType) {
48144826
auto *decl = dyn_cast<ValueDecl>(getDeclContext()->getAsDecl());
@@ -8304,7 +8316,7 @@ ActorReferenceResult ActorReferenceResult::forReference(
83048316
// Treat the decl isolation as 'preconcurrency' to downgrade violations
83058317
// to warnings, because violating Sendable here is accepted by the
83068318
// Swift 5.9 compiler.
8307-
options |= Flags::Preconcurrency;
8319+
options |= Flags::CompatibilityDowngrade;
83088320
return forEntersActor(declIsolation, options);
83098321
}
83108322
}
@@ -8343,8 +8355,10 @@ ActorReferenceResult ActorReferenceResult::forReference(
83438355
// This is a cross-actor reference.
83448356

83458357
// Note if the reference originates from a @preconcurrency-isolated context.
8346-
if (contextIsolation.preconcurrency() || declIsolation.preconcurrency())
8358+
if (contextIsolation.preconcurrency() || declIsolation.preconcurrency()) {
83478359
options |= Flags::Preconcurrency;
8360+
options |= Flags::CompatibilityDowngrade;
8361+
}
83488362

83498363
// If the declaration isn't asynchronous, promote to async.
83508364
if (!decl->isAsync())

lib/Sema/TypeCheckConcurrency.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,20 @@ struct ActorReferenceResult {
208208
/// potentially from a different node, so it must be marked 'distributed'.
209209
Distributed = 1 << 2,
210210

211-
/// The declaration is being accessed from a @preconcurrency context.
211+
/// The declaration is marked as `@preconcurrency` or being accessed
212+
/// from a @preconcurrency context.
212213
Preconcurrency = 1 << 3,
213214

214215
/// Only arguments cross an isolation boundary, e.g. because they
215216
/// escape into an actor in a nonisolated actor initializer.
216217
OnlyArgsCrossIsolation = 1 << 4,
218+
219+
/// The reference to the declaration is invalid but has to be downgraded
220+
/// to a warning because it was accepted by the older compilers or because
221+
/// the declaration predates concurrency and is marked as such.
222+
///
223+
/// NOTE: This flag is set for `Preconcurrency` declarations.
224+
CompatibilityDowngrade = 1 << 5,
217225
};
218226

219227
using Options = OptionSet<Flags>;

lib/Sema/TypeCheckEffects.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1488,7 +1488,8 @@ class ApplyClassifier {
14881488
module = var->getDeclContext()->getParentModule();
14891489
}
14901490
if (!isLetAccessibleAnywhere(module, var, options)) {
1491-
return options.contains(ActorReferenceResult::Flags::Preconcurrency);
1491+
return options.contains(
1492+
ActorReferenceResult::Flags::CompatibilityDowngrade);
14921493
}
14931494
}
14941495

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3588,7 +3588,7 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement,
35883588
isLetAccessibleAnywhere(
35893589
witness->getDeclContext()->getParentModule(),
35903590
var, options);
3591-
if (options.contains(ActorReferenceResult::Flags::Preconcurrency)) {
3591+
if (options.contains(ActorReferenceResult::Flags::CompatibilityDowngrade)) {
35923592
behavior = DiagnosticBehavior::Warning;
35933593
}
35943594
}

test/ClangImporter/objc_async.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ extension SomeWrapper: Sendable where T: Sendable {}
392392
func makeCall(slowServer: SlowServer) {
393393
slowServer.doSomethingSlow("churn butter") { (_ : Int) in
394394
let _ = self.isolatedThing
395-
// expected-warning@-1 {{main actor-isolated property 'isolatedThing' can not be referenced from a Sendable closure; this is an error in the Swift 6 language mode}}
395+
// expected-warning@-1 {{main actor-isolated property 'isolatedThing' can not be referenced from a Sendable closure}}
396396
}
397397
}
398398
}

test/Concurrency/assume_mainactor_typechecker_errors.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class SendableData : @unchecked Sendable {}
1515
// expected-swift5-note@-1 {{calls to initializer 'init()' from outside of its actor context are implicitly asynchronous}}
1616

1717
nonisolated func getDataFromSocket() -> SendableData { SendableData() }
18-
// expected-swift5-warning@-1 {{call to main actor-isolated initializer 'init()' in a synchronous nonisolated context; this is an error in the Swift 6 language mode}}
18+
// expected-swift5-warning@-1 {{call to main actor-isolated initializer 'init()' in a synchronous nonisolated context}}
1919

2020
class Klass { // expected-swift5-note 3 {{}} expected-swift6-note 3 {{}}
2121
let s = SendableData()

test/Concurrency/objc_async_overload.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ extension Delegate {
5858
func handle(_ req: Request, with delegate: Delegate) {
5959
delegate.makeRequest1(req) {
6060
self.finish()
61-
// expected-warning@-1 {{call to main actor-isolated instance method 'finish()' in a synchronous nonisolated context; this is an error in the Swift 6 language mode}}
61+
// expected-warning@-1 {{call to main actor-isolated instance method 'finish()' in a synchronous nonisolated context}}
6262
}
6363
}
6464
}

test/Concurrency/predates_concurrency.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ do {
386386
func run() async {
387387
await test {
388388
if let value {
389-
// expected-warning@-1 {{main actor-isolated property 'value' can not be referenced from a Sendable closure; this is an error in the Swift 6 language mode}}
389+
// expected-warning@-1 {{main actor-isolated property 'value' can not be referenced from a Sendable closure}}
390390
print(value)
391391
}
392392
}

test/Concurrency/predates_concurrency_swift6.swift

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,13 @@ func testElsewhere(x: X) {
6363

6464
func testCalls(x: X) {
6565
// expected-note@-1 2{{add '@MainActor' to make global function 'testCalls(x:)' part of global actor 'MainActor'}}
66-
onMainActorAlways() // expected-error{{call to main actor-isolated global function 'onMainActorAlways()' in a synchronous nonisolated context}}
66+
onMainActorAlways() // expected-warning{{call to main actor-isolated global function 'onMainActorAlways()' in a synchronous nonisolated context}}
6767

6868
let _: () -> Void = onMainActorAlways // expected-error{{converting function value of type '@MainActor @Sendable () -> ()' to '() -> Void' loses global actor 'MainActor'}}
6969

7070
let c = MyModelClass() // okay, synthesized init() is 'nonisolated'
7171

72-
c.f() // expected-error{{call to main actor-isolated instance method 'f()' in a synchronous nonisolated context}}
72+
c.f() // expected-warning{{call to main actor-isolated instance method 'f()' in a synchronous nonisolated context}}
7373
}
7474

7575
func testCallsWithAsync() async {
@@ -338,3 +338,47 @@ func testSendableMetatypeDowngrades() {
338338
acceptsSendableMetatype(t) // Ok (because P is `Sendable`
339339
}
340340
}
341+
342+
// Test that Sendable issues are downgraded to warnings in `@preconcurrency` context.
343+
do {
344+
@preconcurrency nonisolated func f(callback: @Sendable () -> Void) {}
345+
346+
@MainActor
347+
struct Env {
348+
var v: Int = 0
349+
}
350+
351+
@MainActor
352+
class IsolatedTest {
353+
var env = Env()
354+
// expected-note@-1 {{property declared here}}
355+
356+
func onMain() {}
357+
// expected-note@-1 {{calls to instance method 'onMain()' from outside of its actor context are implicitly asynchronous}}
358+
359+
func testProperty() {
360+
f {
361+
_ = self.env.v
362+
// expected-warning@-1 {{main actor-isolated property 'env' can not be referenced from a Sendable closure}}
363+
}
364+
}
365+
366+
func testMethod() {
367+
f {
368+
self.onMain()
369+
// expected-warning@-1 {{call to main actor-isolated instance method 'onMain()' in a synchronous nonisolated context}}
370+
}
371+
}
372+
}
373+
374+
class Test { // expected-note {{class 'Test' does not conform to the 'Sendable' protocol}}
375+
var env = Env()
376+
377+
func testProperty() async {
378+
f {
379+
_ = self.env.v
380+
// expected-warning@-1 {{capture of 'self' with non-Sendable type 'Test' in a '@Sendable' closure}}
381+
}
382+
}
383+
}
384+
}

0 commit comments

Comments
 (0)