I'd also be concerned about recursion: we have to look at associated conformances as well as witnesses to determine if the isolation is correct, and it's possible that we need the same conformance to satisfy one of the associated conformances (possibly through some other intermediate conformance whose isolation is also being inferred).
I don't think we should go down the route of inferring isolation from witnesses/associated conformances because of the technical complexity.
Fair. The reason I brought this up in the first place was because with the original isolated spelling, settling on a default felt more like an irreversible decision (isolating the conformances to the type by default in the future would remove any reason for the standalone isolated spelling to exist) while now with the @MainActor spelling the syntax would remain relevant even if a future language version were to adopt isolated conformances for GAITs by default.
For that issue, maybe cases where inferring an isolated conformance results in a needlessly restricted conformance could be diagnosed? Thinking something like:
@MainActor
struct Foo: Equatable {
`warning: protocol `Equatable` inherits main actor isolation from the type, but its implementation is `nonisolated`
`note: Did you mean `nonisolated Equatable`?
nonisolated static func == (
lhs: Foo,
rhs: Foo
) -> Bool {
// ...
}
Just an idea. I admit it would be odd to diagnose something like that given there's nothing wrong with providing a nonisolated implementation for a @MainActor isolated conformance, it's just unlikely to be what you want. But then in such unlikely scenarios maybe the warning could be suppressed by explicitly opting into an isolated conformance (ie @MainActor Equatable).
Yes, this is possible to do in the compiler. It has the potential to be really annoying, because the compiler is complaining about something it inferred itself and asking the user to write more code to silence it.
I haven't worked this totally through, but once you have eLHS in hand as a value of any Equatable, is there some way to compose this with implicit existential opening to somehow lose track of the isolated nature of the conformance?
Will it be possible to have a protocol that refines isolated version of another protocol? Currently it is not rejected at the point of refinement, but does not work when trying to implement the requirements.
protocol P {
// note: mark the protocol requirement 'foo()' 'async' to allow actor-isolated conformances
func foo()
}
protocol Q: @MainActor P {}
class NS: Q {
// error: main actor-isolated instance method 'foo()' cannot be used to satisfy nonisolated requirement from protocol 'P'
@MainActor func foo() {}
}
This case might be a bit too restrictive, but is sound:
protocol P {
func foo()
}
protocol Q: P {}
// error: conformance of 'NS' to 'Q' depends on main actor-isolated conformance of 'NS' to 'P'; mark it as '@MainActor'
class NS: Q, @MainActor P {
@MainActor func foo() {}
}
But if made a bit more complicated, diagnostics fails:
protocol Q: P {}
protocol R: P {}
class NS: Q, @MainActor R {
// error: main actor-isolated instance method 'foo()' cannot be used to satisfy nonisolated requirement from protocol 'P'
@MainActor func foo() {}
}
And this one crashes the compiler:
class NS: @AnotherActor Q, @MainActor R {
func foo() {}
}
I suppose it could be allowed if the refining protocol was on that same global actor, e.g.,
@MainActor protocol Q: @MainActor P { }
However, "refinement" is really just another way to spell a generic requirement. This would fit in as part of a (significant) expansion to allow requirements themselves to be global-actor-isolated, e.g., Self: @MainActor P.
Right, we have to reject this because otherwise NS: Q could be used off of the main actor, but make its way to the main-actor isolated conformance Q: P.
Here, there's an ambiguity in the conformance of NS to P: is it meant to be nonisolated (as Q implies) or main-actor isolated (as P implies). When there's an ambiguity, the compiler should either pick nonisolated (it seemed to do that here) or ask the user to be more specific. You can't choose @MainActor P here without breaking the nonisolated conformance to Q, though.
Thanks for testing the compiler ;). This needs to be rejected (without crashing), because we need a consistent answer for the isolation of the conformance to P.
When doing as? we cannot use checkIsolated(), but isIsolatingCurrentContext() could be used. But when doing as! it is even possible to use checkIsolated(). But I'm not sure if it is a good idea to have different behavior for as? vs as!.
Great ongoing comments here--thanks everyone! The proposal is now under active review and I'd encourage everyone to continue the conversation over in that thread.
Per Xiaodi's request, I'm going to reply here but ask future questions to go to the review thread.
The implementation essentially depends on that proposal to be able to handle custom executors.
Right. If we end up with a custom executor that doesn't support isIsolatingCurrentContext(), we're somewhat stuck with as?: we could let it pass (that's a data race), possibly with a runtime warning about what's happening, or we could reject it and make conformances isolated to a global actor with a -pre-isIsolatingCurrentContext() executor unusable.
Yes, that's fine. Nonisolated conformances can be used from anywhere (just like nonisolated everything else).