Skip to content

Commit 114d202

Browse files
authored
Add syntax highlighting to SE-0418 (#2245)
* Add syntax highlighting. * Tidy.
1 parent 90081a9 commit 114d202

File tree

1 file changed

+33
-34
lines changed

1 file changed

+33
-34
lines changed

proposals/0418-inferring-sendable-for-methods.md

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ The partial application of methods and other first-class uses of functions have
1818

1919
Let’s look at partial application on its own before we combine it with concurrency. In Swift, you can create a function-value representing a method by writing an expression that only accesses (but does not call) a method using one of its instances. This access is referred to as a "partial application" of a method to one of its (curried) arguments - the object instance.
2020

21-
```
21+
```swift
2222
struct S {
2323
func f() { ... }
2424
}
@@ -30,15 +30,15 @@ let partial: (() -> Void) = S().f
3030
When referencing a method *without* partially applying it to the object instance, using the expression NominalType.method, we call it "unapplied."
3131

3232

33-
```
33+
```swift
3434
let unapplied: (T) -> (() -> Void) = S.f
3535
```
3636

3737

3838
Suppose we want to create a generic method that expects an unapplied function method conforming to Sendable as a parameter. We can create a protocol ``P`` that conforms to the `Sendable` protocol and tell our generic function to expect some generic type that conforms to ``P``. We can also use the `@Sendable` attribute, introduced for closures and functions in [SE-302](https://github.com/kavon/swift-evolution/blob/sendable-functions/proposals/0302-concurrent-value-and-concurrent-closures.md), to annotate the closure parameter.
3939

4040

41-
```
41+
```swift
4242
protocol P: Sendable {
4343
init()
4444
}
@@ -56,7 +56,7 @@ Now let’s call our method and pass our struct type `S` . First we should make
5656
This should make `S` and its methods Sendable as well. However, when we pass our unapplied function `S.f` to our generic function `g`, we get a warning that `S.f` is not Sendable as `g()` is expecting.
5757

5858

59-
```
59+
```swift
6060
struct S: P {
6161
func f() { ... }
6262
}
@@ -67,7 +67,7 @@ g(S.f) // Converting non-sendable function value to '@Sendable (S) -> (() -> Voi
6767

6868
We can work around this by wrapping our unapplied function in a Sendable closure.
6969

70-
```
70+
```swift
7171
// S.f($0) == S.f()
7272
g({ @Sendable in S.f($0) })
7373
```
@@ -79,7 +79,7 @@ However, this is a lot of churn to get the expected behavior. The compiler shoul
7979

8080
[SE-0302](https://github.com/apple/swift-evolution/blob/main/proposals/0302-concurrent-value-and-concurrent-closures.md#key-path-literals) makes an explicit mention that all key path literals are treated as implicitly `Sendable` which means that they are not allowed to capture any non-`Sendable` values. This behavior is justified when key path values are passed across concurrency domains or otherwise involved in concurrently executed code but is too restrictive for non-concurrency related code.
8181

82-
```
82+
```swift
8383
class Info : Hashable {
8484
// some information about the user
8585
}
@@ -114,7 +114,7 @@ We propose the compiler should automatically employ `Sendable` on functions and
114114
For a function, the `@Sendable` attribute primarily influences the kinds of values that can be captured by the function. But methods of a nominal type do not capture anything but the object instance itself. Semantically, a method can be thought of as being represented by the following functions:
115115

116116

117-
```
117+
```swift
118118
// Pseudo-code declaration of a Nominal Type:
119119
type NominalType {
120120
func method(ArgType) -> ReturnType { /* body of method */ }
@@ -136,15 +136,15 @@ func NominalType_method(_ self: NominalType, _ arg1: ArgType) -> ReturnType {
136136
Thus, the only way a partially-applied method can be `@Sendable` is if the `inner` closure were `@Sendable`, which is true if and only if the nominal type conforms to `Sendable`.
137137

138138

139-
```
139+
```swift
140140
type NominalType : Sendable {
141141
func method(ArgType) -> ReturnType { /* body of method */ }
142142
}
143143
```
144144

145145
For example, by declaring the following type `Sendable`, the partial and unapplied function values of the type would have implied Sendability and the following code would compile with no errors.
146146

147-
```
147+
```swift
148148
struct User : Sendable {
149149
func updatePassword (new: String, old: String) -> Bool {
150150
/* update password*/
@@ -163,7 +163,7 @@ Key path literals are very similar to functions, their sendability could be infl
163163

164164
Let’s extend our original example type `User` with a new property and a subscript to showcase the change in behavior:
165165

166-
```
166+
```swift
167167
struct User {
168168
var name: String
169169

@@ -175,46 +175,46 @@ struct User {
175175

176176
A key path to reference a property `name` does not capture any non-Sendable types which means the type of such key path literal could either be inferred as `WritableKeyPath<User, String> & Sendable` or stated to have a sendable type via `& Sendable` composition:
177177

178-
```
178+
```swift
179179
let name = \User.name // WritableKeyPath<User, String> **& Sendable**
180180
let name: KeyPath<User, String> & Sendable = \.name // 🟢
181181
```
182182

183183
It is also allowed to use `@Sendable` function type and `& Sendable` key path interchangeably:
184184

185-
```
185+
```swift
186186
let name: @Sendable (User) -> String = \.name 🟢
187187
```
188188

189189
It is important to note that **under the proposed rule all of the declarations that do not explicitly specify a Sendable requirement alongside key path type are treated as non-Sendable** (see Source Compatibility section for further discussion):
190190

191-
```
191+
```swift
192192
let name: KeyPath<User, String> = \.name // 🟢 but key path is **non-Sendable**
193193
```
194194

195195
Since Sendable is a marker protocol is should be possible to adjust all declarations where `& Sendable` is desirable without any ABI impact.
196196

197197
Existing APIs that use key path in their parameter types or default values can add `Sendable` requirement in a non-ABI breaking way by marking existing declarations as @preconcurrency and adding `& Sendable` at appropriate positions:
198198

199-
```
199+
```swift
200200
public func getValue<T, U>(_: KeyPath<T, U>) { ... }
201201
```
202202

203203
becomes
204204

205-
```
205+
```swift
206206
@preconcurrency public func getValue<T, U>(_: KeyPath<T, U> & Sendable) { ... }
207207
```
208208

209209
Explicit sendability annotation does not override sendability checking and it would still be incorrect to state that the key path literal is Sendable when it captures non-Sendable values:
210210

211-
```
211+
```swift
212212
let entry: KeyPath<User, Entry> & Sendable = \.[Info()] 🔴 Info is a non-Sendable type
213213
```
214214

215215
Such `entry` declaration would be diagnosed by the sendability checker:
216216

217-
```
217+
```swift
218218
warning: cannot form key path that captures non-sendable type 'Info'
219219
```
220220

@@ -237,14 +237,14 @@ struct User : Sendable {
237237

238238
1. The inference of `@Sendable` for unapplied references to methods of a Sendable type.
239239

240-
```
240+
```swift
241241
let unapplied : @Sendable (User)-> ((String, String) -> Void) = User.changeAddress // no error
242242
```
243243

244244
2. The inference of `@Sendable` for partially-applied methods of a Sendable type.
245245

246-
```
247-
`let partial : @Sendable (String, String) -> Void = User().changeAddress // no error`
246+
```swift
247+
let partial : @Sendable (String, String) -> Void = User().changeAddress // no error
248248
```
249249

250250

@@ -253,7 +253,7 @@ These two rules include partially applied and unapplied static methods but do no
253253

254254
3. A key path literal without non-Sendable type captures and references to actor-isolated properties and/or subscripts is going to be inferred as key path type with a `& Sendable` requirement or a function type with `@Sendable` attribute.
255255

256-
```
256+
```swift
257257
extension User {
258258
@MainActor var age: Int { get { 0 } }
259259
}
@@ -266,26 +266,26 @@ The type of age`KP` is `KeyPath<User, Int>` because `age` is isolated to a globa
266266

267267
Key path types respect all of the existing sub-typing rules related to Sendable protocol which means a key path that is not marked as Sendable cannot be assigned to a value that is Sendable.
268268

269-
```
269+
```swift
270270
let name: KeyPath<User, String> = \.name
271271
let otherName: KeyPath<User, String> & Sendable = \.name 🔴
272272
```
273273

274274
The conversion between key path and a `@Sendable` function doesn’t actually require the key path itself to be `Sendable` because the it’s not captured by the closure but wrapped by it.
275275

276-
```
276+
```swift
277277
let name: @Sendable (User) -> String = \.name 🟢
278278
```
279279

280280
The example above is accepted and is transformed by the compiler into:
281281

282-
```
282+
```swift
283283
let name: @Sendable (User) -> String = { $0[keyPath: \.name] }
284284
```
285285

286286
But any subscript arguments that are non-Sendable would preclude the conversion because they’d be captured by the implicitly synthesized closure which makes the closure non-Sendable:
287287

288-
```
288+
```swift
289289
let value: NonSendable = NonSendable()
290290
let _: @Sendable (User) -> String = \.[value] 🔴
291291
```
@@ -296,7 +296,7 @@ Similarly if the conversion captures a key path that has a reference to an isola
296296

297297
Key path literals are allowed to infer Sendability requirements from the context i.e. when a key path literal is passed as an argument to a parameter that requires a Sendable type:
298298

299-
```
299+
```swift
300300
func getValue<T: Sendable>(_: KeyPath<User, T> & Sendable) -> T {}
301301

302302
getValue(name) // 🟢 both parameter & argument match on sendability requirement
@@ -314,9 +314,9 @@ Next is:
314314

315315
Unlike closures, which retain the captured value, global functions can't capture any variables - because global variables are just referenced by the function without any ownership. With this in mind there is no reason not to make these `Sendable` by default. This change will also include static global functions.
316316

317-
```
317+
```swift
318318
func doWork() -> Int {
319-
` Int.random(in: 1..<42)`
319+
Int.random(in: 1..<42)
320320
}
321321

322322
Task<Int, Never>.detached(priority: nil, operation: doWork) // Converting non-sendable function value to '@Sendable () async -> Void' may introduce data races
@@ -326,7 +326,7 @@ Currently, trying to start a `Task` with the global function `doWork` will cause
326326

327327
5. Prohibition of marking methods `@Sendable` when the type they belong to is not `@Sendable`.
328328

329-
```
329+
```swift
330330
class C {
331331
var random: Int = 0 // random is mutable so `C` can't be checked sendable
332332

@@ -343,7 +343,6 @@ Task.detached {
343343
test(num)
344344
}
345345
test(num) // data-race
346-
347346
```
348347

349348
If we move the previous work we wanted to do into a class that stores the random number we generate as a mutable value, we could be introducing a data race by marking the function responsible for this work `@Sendable` . Doing this should be prohibited by the compiler.
@@ -358,7 +357,7 @@ Under the proposed semantics all overloads of this method become non-Sendable bu
358357

359358
Such could be archived by introducing new overloads to `func appending(...)` that utilize `& Sendable` for their parameter and result in an extension of `Sendable` protocol. For example:
360359

361-
```
360+
```swift
362361
extension Sendable where Self: AnyKeyPath {
363362
@inlinable
364363
public func appending<Root, Value, AppendedValue>(
@@ -371,7 +370,7 @@ extension Sendable where Self: AnyKeyPath {
371370

372371
This overload would be selected if both “base” key path and the argument are `Sendable` and would produce a new `Sendable` key path:
373372

374-
```
373+
```swift
375374
func makeUTF8CountKeyPath<Root>(from base: KeyPath<Root, String> & Sendable) -> KeyPath<Root, Int> & Sendable {
376375
// Both `base` and `\String.utf8.count` are Sendable key paths,
377376
// so `appending(path:)` returns a Sendable key path too.
@@ -385,7 +384,7 @@ Standard library would have to introduce a variety of new overloads to keep `Sen
385384

386385
As described in the Proposed Solution section, some of the existing property and variable declarations **without explicit types** could change their type but the impact of the inference change should be very limited. For example, it would only be possible to observe it when a function or key path value which is inferred as Sendable is passed to an API which is overloaded on Sendable capability:
387386

388-
```
387+
```swift
389388
func callback(_: @Sendable () -> Void) {}
390389
func callback(_: () -> Void) {}
391390

@@ -417,7 +416,7 @@ Accessors are not currently allowed to participate with the `@Sendable` system i
417416

418417
Swift could forbid explicitly marking function declarations with the` @Sendable` attribute, since under this proposal there’s no longer any reason to do this.
419418

420-
```
419+
```swift
421420
/*@Sendable*/ func alwaysSendable() {}
422421
```
423422

0 commit comments

Comments
 (0)