The following code works fine without InferIsolatedConformances (with all conformances being nonisolated), but breaks under InferIsolatedConformances (S: P and S: R are inferred as isolated, but S: Q as nonisolated).
protocol P {
@MainActor func foo()
}
@MainActor protocol Q: P {}
protocol R: P {}
@MainActor struct S {
nonisolated init() {}
func foo() {}
}
extension S: Q {} // error: conformance of 'S' to protocol 'Q' crosses into main actor-isolated code and can cause data races
extension S: R {} // ok
Reading the code, I see that conformance S: Q is not inferred to be isolated, because Q itself is actor-isolated, as implemented here.
I'm not sure what is the rationale for this, logic, but I guess it assumes that all requirements in actor-isolated protocol are also actor-isolated, and thus are not affected by isolation of the conformance.
But this assumption is not correct. Protocol can be actor-isolated, and still have nonisolated requirements. And vice versa! So instead of "protocol is non-isolated" the check should be "protocol has at least one nonisolated requirement".
// no need to isolate conformance
protocol A1 {}
@MainActor protocol A2 {}
protocol A3 {
@MainActor func foo()
}
protocol A4: A3 {}
// need to consider isolating conformance
protocol B1 {
func foo()
}
@MainActor protocol B2 {
nonisolated func foo()
}
protocol B3 {
@MainActor func foo()
func bar()
}
Or maybe even skipping this pre-check step, and apply logic that "if there exists an isolated witness to nonisolated requirement" - then infer conformance to be isolated, if not - as nonisolated?
I'm not sure how to deal with @concurrent requirements. They are nonisolated, but they also have their own executor.
@Douglas_Gregor, what do you think?
I think I could do this change. Would it count as a bug fix, or does it warrant an evolution proposal?