Good question. SE-0371: Isolated synchronous deinit seems relevant here since it introduced the possibility for deinitializers to have any other isolation than nonisolated. But SE-0371 says that implicit deinits are always nonisolated:
Implicit deinitializers cannot opt-in into isolation, so they are nonisolated by default.
From your observations, it looks like SE-0466: Control default actor isolation inference changes this rule to "the implicit deinit in a MainActor-by-default class is implicitly isolated", but I couldn't find this being explicitly stated in the proposal.
SE-0466 gives this example showing that a class deinit in a MainActor-by-default context will be implicitly isolated:
// @MainActor
class C {
…
// @MainActor
deinit { ... }
…
}
It's not 100% clear from the proposal text whether this implicit isolation for deinit is only intended for explicit deinit definitions or also for implicit deinits, but either interpretation would seem to contradict your observations (implicit deinit is isolated, explicit deinit in subclass is nonisolated).
According to SE-0371, annotating the deinit with @MainActor isn't wrong, but the recommended syntax is to use isolated deinit { … }.
Are you compiling your code in the Swift 6 language mode? I'm asking because when compiling your code with -language-mode 6, there is another compiler error related to init() in the subclass. This error seems vaguely related, but I also don't understand it:
swiftc -language-mode 6 -default-isolation MainActor test.swift
[SNIP error about deinit]
test.swift:4:17: error: main actor-isolated initializer 'init()' has different actor isolation from nonisolated overridden declaration
1 | class Base {
| `- note: overridden declaration is here
2 | }
3 |
4 | class Sub: Base {
| `- error: main actor-isolated initializer 'init()' has different actor isolation from nonisolated overridden declaration
5 | deinit {
6 | }
My takeaway is that I don't understand the exact rules how MainActor-by-default applies to subclasses.