I took a look at SIL that example generates, and seems like it misses to add hop to the actor executor in case of async.
This is a version of testFunc that's NOT marked as async, here are 2 hops: first hop_to_executor %23... – to the actor. Second is hop_to_executor %3... 2 lines below – to the generic executor.
%21 = load %1 : $*any Foo // users: %23, %22
strong_retain %21 : $any Foo // id: %22
%23 = open_existential_ref %21 : $any Foo to $@opened("61B49E5A-2305-11EF-8A2F-82C93438CB79", any Foo) Self // users: %29, %25, %26, %26, %24
%24 = witness_method $@opened("61B49E5A-2305-11EF-8A2F-82C93438CB79", any Foo) Self, #Foo.testFunc : <Self where Self : Foo> (isolated Self) -> (String) -> String?, %23 : $@opened("61B49E5A-2305-11EF-8A2F-82C93438CB79", any Foo) Self : $@convention(witness_method: Foo) <τ_0_0 where τ_0_0 : Foo> (@guaranteed String, @guaranteed τ_0_0) -> @owned Optional<String> // type-defs: %23; user: %26
hop_to_executor %23 : $@opened("61B49E5A-2305-11EF-8A2F-82C93438CB79", any Foo) Self // id: %25
%26 = apply %24<@opened("61B49E5A-2305-11EF-8A2F-82C93438CB79", any Foo) Self>(%18, %23) : $@convention(witness_method: Foo) <τ_0_0 where τ_0_0 : Foo> (@guaranteed String, @guaranteed τ_0_0) -> @owned Optional<String> // type-defs: %23; users: %118, %58, %28
hop_to_executor %3 : $Optional<Builtin.Executor> // id: %27
debug_value %26 : $Optional<String>, let, name "value" // id: %28
strong_release %23 : $@opened("61B49E5A-2305-11EF-8A2F-82C93438CB79", any Foo) Self // id: %29
Now, when testFunc IS marked as async we see only 1 hop_to_executor %3... after calling it, but no hops to the actor's executor at all, so it is executed at whatever executor it has inherited from task group call of addTask:
%21 = load %1 : $*any Foo // users: %23, %22
strong_retain %21 : $any Foo // id: %22
%23 = open_existential_ref %21 : $any Foo to $@opened("9DFF729A-2305-11EF-97BF-82C93438CB79", any Foo) Self // users: %28, %25, %25, %24
%24 = witness_method $@opened("9DFF729A-2305-11EF-97BF-82C93438CB79", any Foo) Self, #Foo.testFunc : <Self where Self : Foo> (isolated Self) -> (String) async -> String?, %23 : $@opened("9DFF729A-2305-11EF-97BF-82C93438CB79", any Foo) Self : $@convention(witness_method: Foo) @async <τ_0_0 where τ_0_0 : Foo> (@guaranteed String, @guaranteed τ_0_0) -> @owned Optional<String> // type-defs: %23; user: %25
%25 = apply %24<@opened("9DFF729A-2305-11EF-97BF-82C93438CB79", any Foo) Self>(%18, %23) : $@convention(witness_method: Foo) @async <τ_0_0 where τ_0_0 : Foo> (@guaranteed String, @guaranteed τ_0_0) -> @owned Optional<String> // type-defs: %23; users: %117, %57, %27
hop_to_executor %3 : $Optional<Builtin.Executor> // id: %26
debug_value %25 : $Optional<String>, let, name "value" // id: %27
strong_release %23 : $@opened("9DFF729A-2305-11EF-97BF-82C93438CB79", any Foo) Self // id: %28
UPD. Checked on latest Swift toolchain from main branch – still reproduces as well.