File: moveonly_sendable.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (165 lines) | stat: -rw-r--r-- 6,926 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// RUN: %target-typecheck-verify-swift -strict-concurrency=complete -disable-availability-checking

// REQUIRES: concurrency
// REQUIRES: asserts


struct CopyableStruct {}
class Ref { var x = 0 } // expected-note 3{{class 'Ref' does not conform to the 'Sendable' protocol}}

@_moveOnly
struct FileDescriptor: Sendable {
  var id = 0
}

@_moveOnly
enum MaybeFile { // should implicitly conform
  case available(FileDescriptor)
  case closed
}

@_moveOnly
struct NotSendableMO { // expected-note {{consider making struct 'NotSendableMO' conform to the 'Sendable' protocol}}
  // expected-complete-note @-1 {{consider making struct 'NotSendableMO' conform to the 'Sendable' protocol}}
  var ref: Ref
}

// expect no warnings about sendable conformance when crossing actor boundaries:
func invalidFile() async -> FileDescriptor {
  return FileDescriptor(id: -1)
}

func takeNotSendable(_ nsmo: borrowing NotSendableMO) async {}

actor A {
  init(_ t: __owned FileDescriptor) {}
  init (_ t: __owned MaybeFile) {}
  func takeFileDescriptor(_ fd: __owned FileDescriptor) {}
  func takeMaybeFile(_ mfd: __owned MaybeFile) {}
  func giveFileDescriptor() -> MaybeFile {
    return .closed
  }

  func getRef() -> NotSendableMO { return NotSendableMO(ref: Ref()) }
}

@MainActor
func processFiles(_ a: A, _ anotherFile: borrowing FileDescriptor) async {
  let file = await invalidFile()
  await a.takeFileDescriptor(file)

  await a.takeMaybeFile(.available(anotherFile))
  _ = A(.available(anotherFile))

  let ns = await a.getRef() // expected-warning {{non-sendable type 'NotSendableMO' returned by call to actor-isolated function cannot cross actor boundary}}
  await takeNotSendable(ns) // expected-complete-warning {{passing argument of non-sendable type 'NotSendableMO' outside of main actor-isolated context may introduce data races}}

  switch (await a.giveFileDescriptor()) {
  case let .available(fd):
    await a.takeFileDescriptor(fd)
  default:
    break
  }
}

func caller() async {
  await processFiles(A(invalidFile()), invalidFile())
}

// now make sure you can't form a Sendable existential from a move-only type.

@_moveOnly
struct RefPair: Sendable {
  var left: Ref // expected-warning {{stored property 'left' of 'Sendable'-conforming struct 'RefPair' has non-sendable type 'Ref'}}
  var right: Ref  // expected-warning {{stored property 'right' of 'Sendable'-conforming struct 'RefPair' has non-sendable type 'Ref'}}
}

@_moveOnly
enum MaybeRef: Sendable {
  case ref(Ref) // expected-warning {{associated value 'ref' of 'Sendable'-conforming enum 'MaybeRef' has non-sendable type 'Ref'}}
  case null
}

@_moveOnly
enum OK_NoncopyableOption<T: Sendable> : Sendable {
  case some(T)
  case none
}

@_moveOnly
enum Wrong_NoncopyableOption<T> : Sendable { // expected-note {{consider making generic parameter 'T' conform to the 'Sendable' protocol}}
  case some(T) // expected-warning {{associated value 'some' of 'Sendable'-conforming generic enum 'Wrong_NoncopyableOption' has non-sendable type 'T'}}
  case none
}

func takeAnySendable(_ s: any Sendable) {}
func takeSomeSendable(_ s: some Sendable) {} // expected-note {{'some Sendable & Copyable' is implicit here}}

protocol Munchable: ~Copyable {}
struct Chips: ~Copyable, Sendable, Munchable {}
func takeSomeMunchySendable(_ s: some Sendable & Munchable) {} // expected-note {{'some Sendable & Munchable & Copyable' is implicit here}}

// expected-error@+1 {{return expression of type 'FileDescriptor' does not conform to 'Copyable'}}
func mkSendable() -> Sendable { return FileDescriptor(id: 0) }

func tryToCastIt(_ fd: borrowing FileDescriptor) {
  let _: any Sendable = fd // expected-error {{value of type 'FileDescriptor' does not conform to specified type 'Copyable'}}
  let _: Sendable = fd // expected-error {{value of type 'FileDescriptor' does not conform to specified type 'Copyable'}}

  takeAnySendable(fd) // expected-error {{argument type 'FileDescriptor' does not conform to expected type 'Copyable'}}
  takeSomeSendable(fd) // expected-error {{global function 'takeSomeSendable' requires that 'FileDescriptor' conform to 'Copyable'}}
  takeSomeMunchySendable(Chips()) // expected-error {{global function 'takeSomeMunchySendable' requires that 'Chips' conform to 'Copyable'}}

  let _ = fd as Sendable // expected-error {{cannot convert value of type 'FileDescriptor' to type 'any Sendable' in coercion}}

  let _ = fd as? Sendable // expected-warning {{cast from 'FileDescriptor' to unrelated type 'any Sendable' always fails}}
  // expected-error@-1 {{noncopyable types cannot be conditionally cast}}

  let _ = fd as! Sendable // expected-warning {{cast from 'FileDescriptor' to unrelated type 'any Sendable' always fails}}
  // expected-error@-1 {{noncopyable types cannot be conditionally cast}}

  let _ = fd is Sendable // expected-warning {{cast from 'FileDescriptor' to unrelated type 'any Sendable' always fails}}
  // expected-error@-1 {{noncopyable types cannot be conditionally cast}}

  let sendy = mkSendable()
  let _ = sendy as FileDescriptor // expected-error {{cannot convert value of type 'any Sendable' to type 'FileDescriptor' in coercion}}
  let _ = sendy is FileDescriptor // expected-warning {{cast from 'any Sendable' to unrelated type 'FileDescriptor' always fails}}
  // expected-error@-1 {{noncopyable types cannot be conditionally cast}}
  let _ = sendy as! FileDescriptor // expected-warning {{cast from 'any Sendable' to unrelated type 'FileDescriptor' always fails}}
  // expected-error@-1 {{noncopyable types cannot be conditionally cast}}
  let _ = sendy as? FileDescriptor// expected-warning {{cast from 'any Sendable' to unrelated type 'FileDescriptor' always fails}}
  // expected-error@-1 {{noncopyable types cannot be conditionally cast}}
}

protocol GiveSendable<T> {
  associatedtype T: Sendable // expected-note {{protocol requires nested type 'T'; add nested type 'T' for conformance}}
  func give() -> T
}

// make sure witnessing associatedtypes is still prevented, even though we meet the explicit constraint.
class Bad: GiveSendable { // expected-error {{type 'Bad' does not conform to protocol 'GiveSendable'}}
  typealias T = FileDescriptor // expected-note {{possibly intended match 'Bad.T' (aka 'FileDescriptor') does not conform to 'Copyable'}}
  func give() -> FileDescriptor { return FileDescriptor(id: -1) }
}

class Ok: GiveSendable {
  typealias T = CopyableStruct
  func give() -> CopyableStruct { return CopyableStruct() }
}

class Container<T> where T:Sendable {
  var elm: T
  init(_ t: T) { self.elm = t }
}

func createContainer(_ fd: borrowing FileDescriptor) {
  let _: Container<Sendable> = Container(fd) // expected-error {{argument type 'FileDescriptor' does not conform to expected type 'Copyable'}}
  let _: Container<Sendable> = Container(CopyableStruct())
}

@_moveOnly
struct PaperAirplaneFile {
  var fd: FileDescriptor
}

extension PaperAirplaneFile: Sendable {}