Skip to content

Commit 244cce0

Browse files
committed
allow move-only types to conform to Sendable
Part of the reason why we do not want to permit conformance to protocols for move-only types is that they're fundamentally wrong: all existing protocols assume the type is copyable, so we'd be allowing people to write conformances to things that are not actually true. The other aspect of it is that we may need to change the runtime representation for conformance descriptors of move-only types. So we can't have these conformances leaving residue at runtime. Luckily, that means marker protocols would be OK, since they leave no residue at runtime. So for, now we're going to specifically permit move-only types to conform to the marker protocol `Sendable` since it's needed for move-only types to work with concurrency. All of the existing rules about mixing move-only types with generics still apply. That means you still can't turn it into the existential `any Sendable` in any way at all, despite it conforming. The purpose of the conformance is purely to allow the concrete instances to cross actor isolation boundaries if it is actually `Sendable`. resolves rdar://104987062
1 parent 168027b commit 244cce0

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)