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-concurrency
flag 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
async
or@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 aregetValue2
andsetVaue2
allowed, even though we are in Strict Checking mode. Or conversely, why is direct access tovalue2
disallowed, even though it can be easily circumvented with getter/setter functions. - Shouldn't
@preconcurrency
actually be ignored in Sample 1, given that it is in Strict Checking mode. Removing@preconcurrency
resulted 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
value2
is being accessed directly, or also the ones ongetValue2
andsetVaue2
?
Thanks!
Martin