Method marked with globalActor modifying property in class

protocol Po {
    @MainActor
    func foo()
}

class Base: Po {
    var str = ""
    func foo() {
        str = "w"
        print("foo: \(Thread.isMainThread)") // true
    }
}

if #available(macOS 10.15, *) {
    Task {
        let b = Base()
        await b.foo()
    }
} 

RunLoop.main.run()

while true {}

I don't understand why I can modify the property directly in the method that's marked with MainActor?

Because it seems wrong to me.
I believe Base and its property don't run on MainActor.

You create your Task at the top level, which implicitly makes it run on the main actor. Since Base is just a class (not an actor), its methods are run in whatever context its callers are in, which is the main actor in this case.

Sorry, I am asking why foo can directly change the property of str

Why wouldn't it be able to?

Because foo on main thread. But Base shouldn’t be? Because I didn’t mark Base with MainActor

You don't have to. Properties and methods can individually be associated with specific actors, including as part of protocol requirements. In this case foo is implicitly @MainActor because the Po protocol declares it as such.

It is perhaps a little too magical - foo is also implicitly async even though it's never actually marked as such, even in the original protocol declaration.

To test this further, if you add to Base e.g.:

    func bar() {
        foo()
    }

…you'll get the compiler error Call to main actor-isolated instance method 'foo()' in a synchronous nonisolated context, demonstrating that the compiler is treating foo as @MainActor (but that the rest of Base isn't).

Yes, I know foo is on MainActor.
But class Base it isn’t on the MainActor.
So, I would expect str = “xxx” in foo would show an error to me. Because they are on different thread. As my understanding. I may be wrong. Please correct me.