[ConstraintSystem] A non natural behavior of compiler solving sync function even if I use `await` keyword

Environment

swift-driver version: 1.90.11.1 Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)
Target: arm64-apple-macosx14.0

I run this code.

func f(_ a: () async -> Void) async { print("async") } func f(_ a: () -> Void) { print("sync") } func g() async { await f { } } await g() 

output

swift Source.swift Source.swift:8:5: warning: no 'async' operations occur within 'await' expression await f { } ^ sync 

I know why func f(_ a: () -> Void) is called According to an output of -debug-constraints. It is not a kind of bug.
However, I think this behavior is not natural.

I think it would be great always solve async of func when using await keyword.
since I think no one would be happy synchronous is called when we use await keyword.

(In this case, I think it would be great to increase SK_AsyncInSyncMismatch or create a new score in constraint system.)

What do you think?

Why `func f(_ a: () -> Void)` is called?

An output of swiftc -Xfrontend -debug-constraints Source.swift.

---Solver statistics--- Total number of scopes explored: 5 Maximum depth reached while exploring solutions: 3 Time: 8.310000e-01ms Comparing 2 viable solutions --- Solution #0 --- Fixed score: [component: sync-in-asynchronous(s), value: 1] Type variables: $T0 as (() async -> Void) async -> () @ locator@0x153188200 [OverloadedDeclRef@Source.swift:8:11] $T1 as () -> () @ locator@0x153188398 [Closure@Source.swift:8:13] $T2 as () @ locator@0x1531883e0 [Closure@Source.swift:8:13 → closure result] $T3 as () @ locator@0x1531884e8 [Call@Source.swift:8:11 → function result] Overload choices: locator@0x153188200 [OverloadedDeclRef@Source.swift:8:11] with Source.(file).f@Source.swift:1:6 as f: (() async -> Void) async -> () Trailing closure matching: locator@0x153188620 [Call@Source.swift:8:11 → apply argument]: forward --- Solution #1 --- Fixed score: [component: sync-in-asynchronous(s), value: 1] Type variables: $T0 as (() -> Void) -> () @ locator@0x153188200 [OverloadedDeclRef@Source.swift:8:11] $T1 as () -> () @ locator@0x153188398 [Closure@Source.swift:8:13] $T2 as () @ locator@0x1531883e0 [Closure@Source.swift:8:13 → closure result] $T3 as () @ locator@0x1531884e8 [Call@Source.swift:8:11 → function result] Overload choices: locator@0x153188200 [OverloadedDeclRef@Source.swift:8:11] with Source.(file).f@Source.swift:4:6 as f: (() -> Void) -> () Trailing closure matching: locator@0x153188620 [Call@Source.swift:8:11 → apply argument]: forward comparing solutions 1 and 0 Comparing declarations func f(_ a: () -> Void) { } and func f(_ a: () async -> Void) async { } (isDynamicOverloadComparison: 0) (increasing 'sync-in-asynchronous' score by 1 @ locator@0x15318cc00 []) (found solution: [component: sync-in-asynchronous(s), value: 1]) comparison result: better Comparing declarations func f(_ a: () async -> Void) async { } and func f(_ a: () -> Void) { } (isDynamicOverloadComparison: 0) (failed constraint () async -> Void subtype () -> Void @ locator@0x15318dc00 []) comparison result: not better comparing solutions 1 and 0 

the compiler solve overloads based on the rule

  1. The compiler check overloads based on the score rule. func f(_ a: () async -> Void) async and func f(_ a: () -> Void).
  2. The both functions will be scored the same score [component: sync-in-asynchronous(s), value: 1]
  3. When the scores are the same value, the compiler will check which is a subtype in arguments A: _ a: () async -> Void and B: _ a: () -> Void.
  4. The compiler will recognize B is better since B is a subtype of A.
  5. The compiler will solve func f(_ a: () -> Void) according to the result that B is better.
4 Likes