Skip to content

Commit 4513084

Browse files
committed
[Sema] Suppress actor isolation checking for @preconcurrency conformances
Suppress warnings/errors when actor isolated synchroneous witness is matched against `@preconcurrency` conformance requirement. Witness thunk assumes isolation of the witness but instead of a hop to its executor it would emit a runtime check to make sure that its always called from the expected context.
1 parent a26af37 commit 4513084

File tree

3 files changed

+138
-25
lines changed

3 files changed

+138
-25
lines changed

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3182,8 +3182,9 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement,
31823182
}
31833183
}
31843184

3185-
// If we aren't missing anything, do a Sendable check and move on.
3186-
if (!missingOptions) {
3185+
// If we aren't missing anything or this is a witness to a `@preconcurrency`
3186+
// conformance, do a Sendable check and move on.
3187+
if (!missingOptions || Conformance->isPreconcurrency()) {
31873188
// FIXME: Disable Sendable checking when the witness is an initializer
31883189
// that is explicitly marked nonisolated.
31893190
if (isa<ConstructorDecl>(witness) &&
@@ -3210,9 +3211,15 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement,
32103211
if (isAccessibleAcrossActors(witness, refResult.isolation, DC))
32113212
return llvm::None;
32123213

3213-
if (refResult.isolation.isActorIsolated() && isAsyncDecl(requirement) &&
3214-
!isAsyncDecl(witness))
3215-
return refResult.isolation;
3214+
if (refResult.isolation.isActorIsolated()) {
3215+
if (isAsyncDecl(requirement) && !isAsyncDecl(witness))
3216+
return refResult.isolation;
3217+
3218+
// Always treat `@preconcurrency` witnesses as isolated.
3219+
if (Conformance->isPreconcurrency() &&
3220+
missingOptions.contains(MissingFlags::RequirementAsync))
3221+
return refResult.isolation;
3222+
}
32163223

32173224
return llvm::None;
32183225
}

test/Concurrency/preconcurrency_conformances.swift

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// RUN: %target-swift-frontend -disable-availability-checking %s -emit-sil -o /dev/null -verify -enable-experimental-feature PreconcurrencyConformances -verify-additional-prefix minimal-targeted-
2-
// RUN: %target-swift-frontend -disable-availability-checking %s -emit-sil -o /dev/null -verify -enable-experimental-feature PreconcurrencyConformances -strict-concurrency=targeted -verify-additional-prefix minimal-targeted-
31
// RUN: %target-swift-frontend -disable-availability-checking %s -emit-sil -o /dev/null -verify -enable-experimental-feature PreconcurrencyConformances -strict-concurrency=complete -verify-additional-prefix complete-tns-
42

53
// REQUIRES: concurrency
@@ -32,3 +30,77 @@ protocol InvalidUseOfPreconcurrencyAttr : @preconcurrency Q {
3230
struct TestPreconcurrencyAttr {}
3331
extension TestPreconcurrencyAttr : @preconcurrency Q { // Ok
3432
}
33+
34+
class NonSendable {}
35+
// expected-note@-1 6 {{class 'NonSendable' does not conform to the 'Sendable' protocol}}
36+
37+
protocol TestSendability {
38+
var x: NonSendable { get }
39+
func test(_: NonSendable?) -> [NonSendable]
40+
}
41+
42+
// Make sure that preconcurrency conformances don't suppress Sendable diagnostics
43+
@MainActor
44+
struct Value : @preconcurrency TestSendability {
45+
var x: NonSendable { NonSendable() }
46+
// expected-warning@-1 {{non-sendable type 'NonSendable' in conformance of main actor-isolated property 'x' to protocol requirement cannot cross actor boundary}}
47+
// expected-note@-2 2 {{property declared here}}
48+
49+
// expected-warning@+2 {{non-sendable type '[NonSendable]' returned by main actor-isolated instance method 'test' satisfying protocol requirement cannot cross actor boundary}}
50+
// 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}}
51+
func test(_: NonSendable?) -> [NonSendable] {
52+
// expected-note@-1 2 {{calls to instance method 'test' from outside of its actor context are implicitly asynchronous}}
53+
[]
54+
}
55+
}
56+
57+
// Make sure that references to actor isolated witness is diagnosed
58+
59+
// expected-note@+1 2 {{add '@MainActor' to make global function 'test(value:)' part of global actor 'MainActor'}}
60+
func test(value: Value) {
61+
_ = value.x
62+
// expected-error@-1 {{main actor-isolated property 'x' can not be referenced from a non-isolated context}}
63+
_ = value.test(nil)
64+
// expected-error@-1 {{call to main actor-isolated instance method 'test' in a synchronous nonisolated context}}
65+
}
66+
67+
actor MyActor {
68+
var value: Value? = nil
69+
}
70+
71+
extension MyActor : @preconcurrency TestSendability {
72+
var x: NonSendable { NonSendable() }
73+
// expected-warning@-1 {{non-sendable type 'NonSendable' in conformance of actor-isolated property 'x' to protocol requirement cannot cross actor boundary}}
74+
75+
// expected-warning@+2 {{non-sendable type '[NonSendable]' returned by actor-isolated instance method 'test' satisfying protocol requirement cannot cross actor boundary}}
76+
// expected-warning@+1 {{non-sendable type 'NonSendable?' in parameter of the protocol requirement satisfied by actor-isolated instance method 'test' cannot cross actor boundary}}
77+
func test(_: NonSendable?) -> [NonSendable] {
78+
[]
79+
}
80+
81+
func test_ref_diagnostics() {
82+
_ = value?.x
83+
// expected-error@-1 {{main actor-isolated property 'x' can not be referenced on a non-isolated actor instance}}
84+
_ = value?.test(nil)
85+
// expected-error@-1 {{call to main actor-isolated instance method 'test' in a synchronous actor-isolated context}}
86+
}
87+
}
88+
89+
protocol Initializable {
90+
init()
91+
}
92+
93+
final class K : @preconcurrency Initializable {
94+
init() {} // Ok
95+
}
96+
97+
protocol WithAssoc {
98+
associatedtype T
99+
func test() -> T
100+
}
101+
102+
struct TestConditional<T> {}
103+
104+
extension TestConditional : @preconcurrency WithAssoc where T == Int {
105+
@MainActor func test() -> T { 42 } // Ok
106+
}

test/ModuleInterface/preconcurrency_conformances.swift

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,51 @@
11
// RUN: %empty-directory(%t/src)
22
// RUN: split-file %s %t/src
33

4-
/// Build the library
5-
// RUN: %target-swift-frontend -emit-module %t/src/PublicModule.swift \
6-
// RUN: -module-name PublicModule -swift-version 5 -enable-library-evolution \
7-
// RUN: -emit-module-path %t/PublicModule.swiftmodule \
8-
// RUN: -emit-module-interface-path %t/PublicModule.swiftinterface
4+
/// Build the library A
5+
// RUN: %target-swift-frontend -emit-module %t/src/A.swift \
6+
// RUN: -module-name A -swift-version 5 -enable-library-evolution \
7+
// RUN: -emit-module-path %t/A.swiftmodule \
8+
// RUN: -emit-module-interface-path %t/A.swiftinterface
99

1010
// Build the client and check the interface
1111
// RUN: %target-swift-frontend -emit-module %t/src/Client.swift \
1212
// RUN: -module-name Client -I %t -swift-version 5 -enable-library-evolution \
1313
// RUN: -emit-module-path %t/Client.swiftmodule \
1414
// RUN: -emit-module-interface-path %t/Client.swiftinterface \
15-
// RUN: -enable-experimental-feature PreconcurrencyConformances
15+
// RUN: -enable-experimental-feature PreconcurrencyConformances \
16+
// RUN: -verify
1617

1718
// RUN: %FileCheck %s < %t/Client.swiftinterface
1819

19-
// RUN: %target-swift-emit-module-interface(%t/Client.swiftinterface) -I %t %s -module-name Client \
20-
// RUN: -enable-experimental-feature PreconcurrencyConformances
20+
// RUN: %target-swift-emit-module-interface(%t/Client.swiftinterface) -I %t %t/src/Client.swift -module-name Client \
21+
// RUN: -enable-experimental-feature PreconcurrencyConformances -verify
2122

2223
// RUN: %target-swift-typecheck-module-from-interface(%t/Client.swiftinterface) -I %t -module-name Client \
23-
// RUN: -enable-experimental-feature PreconcurrencyConformances
24+
// RUN: -enable-experimental-feature PreconcurrencyConformances -verify
2425

2526
// REQUIRES: asserts
2627
// REQUIRES: concurrency
2728

28-
//--- PublicModule.swift
29+
//--- A.swift
2930
public protocol P {
3031
func test() -> Int
3132
}
3233

34+
public protocol Q {
35+
var x: Int { get }
36+
}
37+
38+
public protocol WithAssoc {
39+
associatedtype T
40+
func test() -> T
41+
}
42+
3343
//--- Client.swift
34-
import PublicModule
44+
import A
45+
46+
// CHECK: #if {{.*}} $PreconcurrencyConformances
47+
// CHECK-NEXT: @_Concurrency.MainActor public struct GlobalActorTest : @preconcurrency A.P
3548

36-
// CHECK: #if compiler(>=5.3) && $PreconcurrencyConformances
37-
// CHECK-NEXT: @_Concurrency.MainActor public struct GlobalActorTest : @preconcurrency PublicModule.P
3849
@MainActor
3950
public struct GlobalActorTest : @preconcurrency P {
4051
public func test() -> Int { 0 }
@@ -44,14 +55,37 @@ public struct GlobalActorTest : @preconcurrency P {
4455
public class ExtTest {
4556
}
4657

47-
// CHECK: #if compiler(>=5.3) && $PreconcurrencyConformances
48-
// CHECK-NEXT: extension Client.ExtTest : @preconcurrency PublicModule.P
58+
// CHECK: #if {{.*}} $PreconcurrencyConformances
59+
// CHECK-NEXT: extension Client.ExtTest : @preconcurrency A.P
4960
extension ExtTest : @preconcurrency P {
5061
public func test() -> Int { 1 }
5162
}
5263

53-
// CHECK: #if compiler(>=5.3) && $PreconcurrencyConformances
64+
// CHECK: #if {{.*}} && $PreconcurrencyConformances
65+
// CHECK-NEXT: public actor ActorTest : @preconcurrency A.P
66+
public actor ActorTest : @preconcurrency P {
67+
public func test() -> Int { 2 }
68+
}
69+
70+
public actor ActorExtTest {
71+
}
72+
73+
// CHECK: #if {{.*}} $PreconcurrencyConformances
74+
// CHECK-NEXT: extension Client.ActorExtTest : @preconcurrency A.Q
75+
extension ActorExtTest : @preconcurrency Q {
76+
public var x: Int { 42 }
77+
}
78+
79+
public struct TestConditional<T> {}
80+
81+
// CHECK: #if {{.*}} $PreconcurrencyConformances
82+
// CHECK-NEXT: extension Client.TestConditional : @preconcurrency A.WithAssoc where T == Swift.Int {
83+
// CHECK-NEXT: @_Concurrency.MainActor public func test() -> T
84+
// CHECK-NEXT: }
85+
extension TestConditional : @preconcurrency WithAssoc where T == Int {
86+
@MainActor public func test() -> T { 42 } // Ok
87+
}
88+
89+
// CHECK: #if {{.*}} $PreconcurrencyConformances
5490
// CHECK-NEXT: extension Client.GlobalActorTest : Swift.Sendable {}
5591
// CHECK-NEXT: #endif
56-
57-
// TODO: 'actor' cannot be tested until @preconcurrency conformances are implemented.

0 commit comments

Comments
 (0)