Skip to content

Commit bbd7946

Browse files
authored
Merge pull request #63427 from kavon/sendable-moveonly
allow move-only types to conform to `Sendable`
2 parents 07ad2b6 + 244cce0 commit bbd7946

File tree

3 files changed

+159
-7
lines changed

3 files changed

+159
-7
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4590,13 +4590,6 @@ ProtocolConformance *GetImplicitSendableRequest::evaluate(
45904590
if (isa<ProtocolDecl>(nominal))
45914591
return nullptr;
45924592

4593-
// Move only nominal types are currently never sendable since we have not yet
4594-
// finished the generics model for them.
4595-
//
4596-
// TODO: Remove this once this is complete!
4597-
if (nominal->isMoveOnly())
4598-
return nullptr;
4599-
46004593
// Actor types are always Sendable; they don't get it via this path.
46014594
auto classDecl = dyn_cast<ClassDecl>(nominal);
46024595
if (classDecl && classDecl->isActor())

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2732,6 +2732,12 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
27322732
return;
27332733

27342734
for (auto *prot : nomDecl->getLocalProtocols()) {
2735+
// Permit conformance to marker protocol Sendable.
2736+
if (prot->isSpecificProtocol(KnownProtocolKind::Sendable)) {
2737+
assert(prot->isMarkerProtocol());
2738+
continue;
2739+
}
2740+
27352741
nomDecl->diagnose(diag::moveonly_cannot_conform_to_protocol_with_name,
27362742
nomDecl->getDescriptiveKind(),
27372743
nomDecl->getBaseName(), prot->getBaseName());

test/Sema/moveonly_sendable.swift

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-move-only -strict-concurrency=complete -disable-availability-checking
2+
3+
// REQUIRES: concurrency
4+
5+
6+
struct CopyableStruct {}
7+
class Ref { var x = 0 } // expected-note 3{{class 'Ref' does not conform to the 'Sendable' protocol}}
8+
9+
@_moveOnly
10+
struct FileDescriptor: Sendable {
11+
var id = 0
12+
}
13+
14+
@_moveOnly
15+
enum MaybeFile { // should implicitly conform
16+
case available(FileDescriptor)
17+
case closed
18+
}
19+
20+
@_moveOnly
21+
struct NotSendableMO { // expected-note 2{{consider making struct 'NotSendableMO' conform to the 'Sendable' protocol}}
22+
var ref: Ref
23+
}
24+
25+
// expect no warnings about sendable conformance when crossing actor boundaries:
26+
func invalidFile() async -> FileDescriptor {
27+
return FileDescriptor(id: -1)
28+
}
29+
30+
func takeNotSendable(_ nsmo: NotSendableMO) async {}
31+
32+
actor A {
33+
init(_ t: FileDescriptor) {}
34+
init (_ t: MaybeFile) {}
35+
func takeFileDescriptor(_ fd: __owned FileDescriptor) {}
36+
func takeMaybeFile(_ mfd: __owned MaybeFile) {}
37+
func giveFileDescriptor() -> MaybeFile {
38+
return .closed
39+
}
40+
41+
func getRef() -> NotSendableMO { return NotSendableMO(ref: Ref()) }
42+
}
43+
44+
@MainActor
45+
func processFiles(_ a: A, _ anotherFile: FileDescriptor) async {
46+
let file = await invalidFile()
47+
await a.takeFileDescriptor(file)
48+
49+
await a.takeMaybeFile(.available(anotherFile))
50+
_ = A(.available(anotherFile))
51+
52+
let ns = await a.getRef() // expected-warning {{non-sendable type 'NotSendableMO' returned by implicitly asynchronous call to actor-isolated instance method 'getRef()' cannot cross actor boundary}}
53+
await takeNotSendable(ns) // expected-warning {{non-sendable type 'NotSendableMO' exiting main actor-isolated context in call to non-isolated global function 'takeNotSendable' cannot cross actor boundary}}
54+
55+
switch (await a.giveFileDescriptor()) {
56+
case let .available(fd):
57+
await a.takeFileDescriptor(fd)
58+
default:
59+
break
60+
}
61+
}
62+
63+
func caller() async {
64+
await processFiles(A(invalidFile()), invalidFile())
65+
}
66+
67+
// now make sure you can't form a Sendable existential from a move-only type.
68+
69+
@_moveOnly
70+
struct RefPair: Sendable {
71+
var left: Ref // expected-warning {{stored property 'left' of 'Sendable'-conforming struct 'RefPair' has non-sendable type 'Ref'}}
72+
var right: Ref // expected-warning {{stored property 'right' of 'Sendable'-conforming struct 'RefPair' has non-sendable type 'Ref'}}
73+
}
74+
75+
@_moveOnly
76+
enum MaybeRef: Sendable {
77+
case ref(Ref) // expected-warning {{associated value 'ref' of 'Sendable'-conforming enum 'MaybeRef' has non-sendable type 'Ref'}}
78+
case null
79+
}
80+
81+
@_moveOnly
82+
enum OK_NoncopyableOption<T: Sendable> : Sendable {
83+
case some(T)
84+
case none
85+
}
86+
87+
@_moveOnly
88+
enum Wrong_NoncopyableOption<T> : Sendable { // expected-note {{consider making generic parameter 'T' conform to the 'Sendable' protocol}}
89+
case some(T) // expected-warning {{associated value 'some' of 'Sendable'-conforming generic enum 'Wrong_NoncopyableOption' has non-sendable type 'T'}}
90+
case none
91+
}
92+
93+
func takeAnySendable(_ s: any Sendable) {}
94+
func takeSomeSendable(_ s: some Sendable) {}
95+
96+
// expected-error@+1 {{move-only type 'FileDescriptor' cannot be used with generics yet}}
97+
func mkSendable() -> Sendable { return FileDescriptor(id: 0) }
98+
99+
func tryToCastIt(_ fd: FileDescriptor) {
100+
let _: any Sendable = fd // expected-error {{move-only type 'FileDescriptor' cannot be used with generics yet}}
101+
let _: Sendable = fd // expected-error {{move-only type 'FileDescriptor' cannot be used with generics yet}}
102+
103+
takeAnySendable(fd) // expected-error {{move-only type 'FileDescriptor' cannot be used with generics yet}}
104+
takeSomeSendable(fd) // expected-error {{move-only type 'FileDescriptor' cannot be used with generics yet}}
105+
106+
let _ = fd as Sendable // expected-error {{move-only type 'FileDescriptor' cannot be used with generics yet}}
107+
108+
let _ = fd as? Sendable // expected-warning {{cast from 'FileDescriptor' to unrelated type 'any Sendable' always fails}}
109+
// expected-error@-1 {{marker protocol 'Sendable' cannot be used in a conditional cast}}
110+
111+
let _ = fd as! Sendable // expected-warning {{cast from 'FileDescriptor' to unrelated type 'any Sendable' always fails}}
112+
113+
let _ = fd is Sendable // expected-warning {{cast from 'FileDescriptor' to unrelated type 'any Sendable' always fails}}
114+
// expected-error@-1 {{marker protocol 'Sendable' cannot be used in a conditional cast}}
115+
}
116+
117+
protocol GiveSendable<T> {
118+
associatedtype T: Sendable // expected-note {{protocol requires nested type 'T'; do you want to add it?}}
119+
func give() -> T
120+
}
121+
122+
// make sure witnessing associatedtypes is still prevented, even though we meet the explicit constraint.
123+
class Bad: GiveSendable { // expected-error {{type 'Bad' does not conform to protocol 'GiveSendable'}}
124+
typealias T = FileDescriptor // expected-note {{possibly intended match 'Bad.T' (aka 'FileDescriptor') does not conform to '_Copyable'}}
125+
func give() -> FileDescriptor { return FileDescriptor(id: -1) }
126+
}
127+
128+
class Ok: GiveSendable {
129+
typealias T = CopyableStruct
130+
func give() -> CopyableStruct { return CopyableStruct() }
131+
}
132+
133+
class Container<T> where T:Sendable {
134+
var elm: T
135+
init(_ t: T) { self.elm = t }
136+
}
137+
138+
func createContainer(_ fd: FileDescriptor) {
139+
let _: Container<Sendable> = Container(fd) // expected-error {{move-only type 'FileDescriptor' cannot be used with generics yet}}
140+
let _: Container<Sendable> = Container(CopyableStruct())
141+
}
142+
143+
func takeTwo<T: Sendable>(_ s1: T, _ s2: T) {}
144+
145+
extension Sendable {
146+
func doIllegalThings() {
147+
return takeTwo(self, self)
148+
}
149+
}
150+
151+
func tryToDupe(_ fd: FileDescriptor) {
152+
fd.doIllegalThings() // expected-error {{move-only type 'FileDescriptor' cannot be used with generics yet}}
153+
}

0 commit comments

Comments
 (0)