Hi everyone.
I am planning out the incremental migration process of a project to the new concurrency system. I've read through SE-0337 and started experimenting with @preconcurrency and -warn-concurrency and here are my results:
Sample 1
// Xcode 13.4; Other swift flags: -Xfrontend -warn-concurrency
@preconcurrency @MainActor
class SomeViewModel {
let value1 = 0
var value2 = 0
func getValue2() -> Int { value2 }
func setValue2(_ newValue: Int) { value2 = newValue }
}
func doSomething(with viewModel: SomeViewModel) {
_ = viewModel.value1
_ = viewModel.value2 // ERROR: Property 'value2' isolated to global actor 'MainActor' can not be referenced from this synchronous context
_ = viewModel.getValue2()
viewModel.setValue2(3)
}
My understanding here is that doSomething should be using Strict Checking mode because it's in the top level scope and -warn-concurrency is being used. From the proposal:
The top level scope's concurrency checking mode is:
- Strict when the module is being compiled in Swift 6 mode or later, when the
-warn-concurrencyflag is used with an earlier language mode, or when the file being parsed is a module interface.- Minimal otherwise.
Sample 2
// Xcode 13.4; Other swift flags:
@preconcurrency @MainActor
class SomeViewModel {
let value1 = 0
var value2 = 0
func getValue2() -> Int { value2 }
func setValue2(_ newValue: Int) { value2 = newValue }
}
func doSomething(with viewModel: SomeViewModel) {
// no warnings or erros here
_ = viewModel.value1
_ = viewModel.value2
_ = viewModel.getValue2()
viewModel.setValue2(3)
}
func wrappingAsync() async {
func doSomethingElse(with viewModel: SomeViewModel) {
_ = viewModel.value1
_ = viewModel.value2 // ERROR: Property 'value2' isolated to global actor 'MainActor' can not be referenced from this synchronous context
_ = viewModel.getValue2() // ERROR: Call to main actor-isolated instance method 'getValue2()' in a synchronous nonisolated context
viewModel.setValue2(3) // ERROR: Call to main actor-isolated instance method 'getValue2()' in a synchronous nonisolated context
}
}
In this example, my understanding is that the top level scope would be using Minimal Checking mode (we're in Swift 5.6 without the -warn-concurrency flag). However wrappingAsync() should be using Strict Checking because it is an async function, and then doSomethingElse should also be using Strict Checking because its parent scope is in strict checking mode.
A child scope's concurrency checking mode is:
- Strict if the parent's concurrency checking mode is Minimal and any of the following conditions is true of the child scope:
- ...
- It is a function, method, initializer, accessor, variable, or subscript which is marked
asyncor@Sendable.- ...
- Otherwise, the same as the parent scope's.
Questions
- Am I missing something obvious? E.g. some flag or perhaps my understanding of scopes is wrong?
- In
Sample 1, why aregetValue2andsetVaue2allowed, even though we are in Strict Checking mode. Or conversely, why is direct access tovalue2disallowed, even though it can be easily circumvented with getter/setter functions. - Shouldn't
@preconcurrencyactually be ignored in Sample 1, given that it is in Strict Checking mode. Removing@preconcurrencyresulted in the same set of errors as in Sample 2. - In case both of those samples work as expected, if hypothetically Swift 6 was available right now and I used it in those samples, which set of errors should I expect - just the ones where
value2is being accessed directly, or also the ones ongetValue2andsetVaue2?
Thanks!
Martin