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
- The compiler check overloads based on the score rule.
func f(_ a: () async -> Void) async
andfunc f(_ a: () -> Void)
. - The both functions will be scored the same score
[component: sync-in-asynchronous(s), value: 1]
- 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
. - The compiler will recognize B is better since B is a subtype of A.
- The compiler will solve
func f(_ a: () -> Void)
according to the result that B is better.