This is possible because actors do not support inheritance. There
is one specific exception to that rule, which is that an actor
can inherit from `NSObject` just to support ObjC interop.
This means an actor is effectively a final class.
resolves rdar://87568153
The flow-isolation pass was not respecting the new strict-concurrency checking mode.
Since the Sendable diagnostics in these deinits are very noisy, I'm moving them to only
be emitted in 'complete' mode. The reason why they're so noisy is that any class that
inherits from a `@MainActor`-constrained class will have these diagnostics emitted when
trying to access its own `@MainActor`-isolated members.
This is needed, even during the `deinit`, because multiple instances of a `@MainActor`-isolated
class might have stored properties that refer to the same state.
This change specifically avoids emitting these diagnostics even in 'targeted' mode because
I'd like to take more time to reconsider the ergonomics of these deinits.
resolves rdar://94699928
In the replacement of the escaping-use restriction with
flow-isolation, I hadn't accounted for all of the situations
where the isolation changes would break backwards compatability
with Swift 5.5 programs. The escaping-use restriction permitted
a lot of very unsafe things with warnings that it would become
an error in Swift 6.
With the introduction of flow-isolation, it was a bit tricky to
get the right warnings back in place, while not unnessecarily
warning about property accesses that might actually be OK. There
is a very careful coordination between the type-checker and
the flow-isolation pass.
While I had done these downgrades for deinits, I also needed to
do them for inits as well, because member accesses to isolated
methods within actor initializer were still getting rejected
as an error. This patch should be pretty solid now.
fixes rdar://90595278
When emitting the note to point out what introduced
nonisolation, the code building the message assumed that
`DeclContext::getDecl` will not return null, when it can
if it's a closure expression.
fixes rdar://88776902
This capability was available in Swift 5.5, but with flow-isolation
it's not safe to do so in general. When I initially implemented
flow-isolation, I intended for it to not break too much existing
code, and emit warnings about things changing in Swift 6. But
I missed this case, where we have just a simple method call in
a deinit, which is likely to be common:
```swift
actor A {
func cleanup() { ... }
deinit {
cleanup()
}
}
```
Instead of rejecting that call to `cleanup`, we now warn that it's
not going to be allowed in Swift 6, because `cleanup` is isolated
and the deinit is not.
This is a combination of fixes:
- inject hops in self-isolated delegating actor initializers
after self becomes initialized.
- fix sendable and isolation for convenience inits
- fix bug in distributed actor inits that I introduced when
implementing flow-isolation.
- fix / add test coverage.
Flow-isolation is a diagnostic SIL pass that finds
unsafe accesses to properties in initializers and
deinitializers that cannot gain isolation to otherwise
protect those accesses from concurrent modifications.
See SE-327 for more details about how and why it exists.
This commit includes changes and features like:
- The removal of the escaping-use restriction
- Flow-isolation that works properly with `defer` statements
- Flow-isolation with an emphasis on helpful diagnostics.
It also includes known issues like:
- Local / nonescaping functions are not analyzed by
flow-isolation, despite it being technically possible.
The main challenge in supporting it efficiently is that
such functions do not have a single exit-point, like
a `defer`. In particular, arbitrary functions can throw
so there are points where nonisolation should _not_ flow
out of the function at a call-site in the initializer, etc.
- The implementation of the flow-isolation pass is not
particularly memory efficient; it relies on BitDataflow
even though the particular flow problem is simple.
So, a more efficient implementation would be specialized for
this particular problem, etc.
There are also some changes to the Swift language itself: defer
will respect its context when deciding its property access kind.
Previously, a defer in an initializer would always access a stored
property through its accessor methods, instead of doing so directly
like its enclosing function might. This inconsistency is unfortunate,
so for Swift 6+ we make this consistent. For Swift 5, only a defer
in a function that is a member of the following kinds of types
will gain this consistency:
- an actor type
- any nominal type that is actor-isolated, excluding UnsafeGlobalActor.
These types are still rather new, so there is much less of a chance of
breaking expected behaviors around defer. In particular, the danger is
that users are relying on the behavior of defer triggering a property
observer within an init or deinit, when it would not be triggering it
without the defer.