💯

Swiftのオーバーロード選択のスコア規則21種類

2024/04/15に公開

はじめに

この記事は、2019年(当時はSwift5.1.2)にomochimetaruさんがわいわいswiftc#16にて発表した Swiftのオーバーロード選択のスコア規則12種類 のSwift5.10版です。
当時のスコア規則は12種類でしたが、Swift5.10では9種類増えた21種類となっています。

https://speakerdeck.com/omochi/swiftfalseobarodoxuan-ze-falsesukoagui-ze-12zhong-lei

オーバーロード選択のスコア規則とは

オーバーロードと型推論

Swiftにはオーバーロードがあります。

func f(_ a: String) { print("String") }
func f(_ a: Int) { print("Int") }

let a: Int = 1
f(a) // => Int

コンパイラはある関数呼び出しがどの関数定義(オーバーロード)に対応するかを推論します。
どの関数を選ぶかは型推論と相互に関係するため、オーバーロード選択は型推論機構に統合されています。

型推論器は複数の有効な解を発見することがあります。

func f(_ a: Int?) { print("Int?") }
func f(_ a: Int) { print("Int") }

let a: Int = 1
f(a) // => Int

この時、優先度規則を用いて複数の解から最優先解を決定します。
優先度規則の最も基本となる規則がスコア規則です。

スコア規則

それぞれの推論解に対してスコアを計算します。
スコアの値が高いほど解として優先度が低くなります。つまりコストやペナルティを表しています。

推論解は21種類の評価軸で採点され、各評価軸毎に点数が付きます。この点数をスコアと呼びます。
最終的には、この21個のスコアを用いて解を決定します。
21種のスコアには優劣があり、これを本記事では1-21の整数を割り振ってスコアランクと呼ぶことにします。スコアランクが高い方がよりコストが重いです。

本記事では、スコアを[#スコアランク スコア名: 値, (...複数ある場合は繰り返し)]として表記します。

// [#7 SK_ValueToOptional: 1]
func f(_ a: Int?) { print("Optional") }
// [#6 SK_EmptyExistentialConversion: 1]
func f(_ a: Any) { print("Any") }

let a: Int = 1
f(a) // => Any

例えば上記の例では、それぞれのオーバーロードで1種類のスコアに1点づつ付いており
func f(_ a: Any)についているスコアのスコアランクの方が低いため、解となります。

スコア一覧

定義

https://github.com/apple/swift/blob/swift-5.10-RELEASE/include/swift/Sema/ConstraintSystem.h#L933C1-L998C3

ランク1: SK_UnappliedFunction

UnappliedFunctionに付くスコア。

func a(_ b: Int) {}

func f(_ a: (Int) -> Void) {}
f(a)

func a(_ b: Int) {}
var a = 0

// [#1 SK_UnappliedFunction: 1]
func f(_ a: (Int) -> Void) { 
    print("func") 
}
// [](スコアなし)
func f(_ a: Int) { 
    print("var")
}
f(a) // => var
実装PR

https://github.com/apple/swift/pull/39390

PRに書いてあった頻出例

extension Sequence where Element == Int {
    // var count ...

    func count(
        _ predicate: (Int) -> Bool
    ) -> Int { ... }
}

func f(_ a: ((Int) -> Bool) -> Int) { 
    print("func") 
}
func f(_ a: Int) { 
    print("var") 
}

f([1, 2, 3].count) // => var

ランク2: SK_MissingSynthesizableConformance

Sendableに準拠していない型」から「Sendableに準拠している型」の暗黙変換に付くスコア。

// non sendable type
class A {
    var value = 10
}

func f<T: Sendable>(_ a: T) {}
f(A())

// non sendable type
class A {
    var value = 10
}

// [#2 SK_MissingSynthesizableConformance: 1]
func f<T: Sendable>(_ a: T) { print("sendable") }
// []
func f<T>(_ a: T) { print("non sendable") }

f(A()) // => non sendable
実装PR

ランク3: SK_FunctionToAutoClosureConversion

@autoclosure関連で付くスコア。

@autoclosure引数をクロージャーで記述した場合

func f<T>(_ a: @autoclosure () -> T) { }
f { 1 }

f { 1 }f<T>(_ a: @autoclosure () -> T)で解決する場合T() -> Intとして解決されてしまい煩雑になる。これを回避したい意図が考えられる。

// [#3 SK_FunctionToAutoClosureConversion: 1]
func f<T>(_ a: @autoclosure () -> T) { print("autoclosure") }
// []
func f<T>(_ a: () -> T) { print("non autoclosure") }

f { 1 } // => non autoclosure
実装PR

@autoclosure引数がある関数」から「@autoclosure引数のない関数型」に変換する場合

func f<T>(_ a: @autoclosure () -> T) { }
let _ = f as (() -> Int) -> ()

func f<T>(_ a: @autoclosure () -> T) { print("autoclosure") }
func f<T>(_ a: () -> T) { print("non autoclosure") }

let g = f as (() -> Int) -> ()
g { 1 } // => non autoclosure
実装PR

ランク4: SK_ValueToPointerConversion

String, Array, inoutからポインタへの暗黙変換に付くスコア。

func f(_ a: UnsafePointer<Int>) { }

let a: [Int] = [1, 2, 3]
f(a)

// [#4 SK_ValueToPointerConversion: 1]
func f(_ a: UnsafePointer<Int>) { print("Pointer") }
// []
func f(_ a: Int) { print("Int") }

let a: [Int] = [1, 2, 3]
f(a) // => Int
実装コミット

ランク5: SK_KeyPathSubscript

KeyPathsubscript関連で付くスコア。

デフォルトのsubscript(keyPath keyPath:実装を呼ぶ場合

struct A {
    var name: String = "var"
}
A()[keyPath: \.name]

struct A {
    // [#5 SK_KeyPathSubscript: 1]
    var name: String = "var"

    // []
    subscript(keyPath keyPath: KeyPath<A, String>) -> String {
        return "subscript"
    }
}

print(A()[keyPath: \.name]) // => subscript
実装PR

@dynamicMemberLookupsubscript(dynamicMember keyPath:を直接呼ぶ場合

@dynamicMemberLookup
struct A {
    struct B { var value = "b value" }
    
    var b = B()

    subscript(dynamicMember keyPath: KeyPath<B, String>) -> String {
        "dynamicMember"
    }
}

A()[keyPath: \.value]

@dynamicMemberLookup
struct A {
    struct B { var value = "b value" }
    
    var b = B()

    // [#5 SK_KeyPathSubscript: 1]
    subscript(dynamicMember keyPath: KeyPath<B, String>) -> String {
        "dynamicMember"
    }

    // []
    subscript(keyPath keyPath: KeyPath<B, String>) -> String {
        "subscript"
    }
}

print(A()[keyPath: \.value]) // => subscript
実装PR

ランク6: SK_EmptyExistentialConversion

Anyへの暗黙変換に付くスコア。

func f(_ a: Any) {}

let a: Int = 1
f(a)

// [#6 SK_EmptyExistentialConversion: 1]
func f(_ a: Any) { print("Any") }
// [#4 SK_ValueToPointerConversion: 1]
func f(_ a: UnsafePointer<Int>) { print("Pointer") }

let a: [Int] = [1, 2, 3]
f(a) // => Pointer
実装PR

ランク7: SK_ValueToOptional

Optionalへの暗黙変換に付くスコア。

func f(_ a: Int?) {}

let a: Int = 1
f(a)

// [#7 SK_ValueToOptional: 1]
func f(_ a: Int?) { print("Optional") }
// [#6 SK_EmptyExistentialConversion: 1]
func f(_ a: Any) { print("Any") }

let a: Int = 1
f(a) // => Any
実装コミット

ランク8: SK_CollectionUpcastConversion

Array, Dictionary, Setの暗黙アップキャストに付くスコア。

class Super {}
class Sub: Super {}

func f(_ a: [Super]) {}

let a: [Sub] = [Sub()]
f(a)

class Super {}
class Sub: Super {}

// [#8 SK_CollectionUpcastConversion: 1, #7 SK_ValueToOptional: 1]
func f(_ a: [Sub?]) { print("[Sub?]") }
// [#8 SK_CollectionUpcastConversion: 1, #6 SK_EmptyExistentialConversion: 1]
func f(_ a: [Any]) { print("[Any]") }
// [#8 SK_CollectionUpcastConversion: 1]
func f(_ a: [Super]) { print("[Super]") }

let a: [Sub] = [Sub()]
f(a) // => [Super]
実装コミット

ランク9: SK_NonDefaultLiteral

「各リテラルのデフォルト型」と型が不一致の場合に付くスコア。

func f(_ a: Float) {}

f(0) // IntegerLiteralのデフォルト型はInt

// [#9 SK_NonDefaultLiteral: 1]
func f(_ a: Float) { print("Float") }
// [#7 SK_ValueToOptional: 1]
func f(_ a: Int?) { print("Int?") }

f(0) // => Int?
実装コミット

ランク10: SK_FunctionConversion

関数型に関連する暗黙の型変換につくスコア。

関数型の引数や返り値が暗黙変換(継承関係・オプショナル化・Any化など)する場合

class Super {}
class Sub: Super {}

func f(_ g: () -> Super) { }
func g() -> Sub { Sub() }

f(g)

class Super {}
class Sub: Super {}

// [#10 SK_FunctionConversion: 1, #7 SK_ValueToOptional: 1]
func f(_ g: () -> Sub?) { print("() -> Sub?") }
// [#10 SK_FunctionConversion: 1]
func f(_ g: () -> Super) { print("() -> Super") }
// [#7 SK_ValueToOptional: 1]
func f(_ g: (() -> Sub)?) { print("(() -> Sub)?") }

func g() -> Sub { Sub() }

f(g) // => (() -> Sub)?
実装コミット

KeyPathが関数型に暗黙変換する場合

struct A {
    var a: Int = 10
}

func f(_ a: (A) -> Int) { print("(A) -> Int") }

f(\A.a)

struct A {
    var a: Int = 10
}

// [#10 SK_FunctionConversion: 1]
func f(_ a: (A) -> Int) { print("(A) -> Int") }
// []
func f(_ a: KeyPath<A, Int>) { print("Keypath") }

f(\A.a) // => Keypath

実装PR: 探し中。情報求む。

() -> Void型に() -> Tとして推論されるクロージャー記法を書く場合。(クロージャー記法のみ かつ Implicit returns from single-expression functionsによってreturnを書かない場合のみ)

func f(_ a: () -> Void) {}
f { 10 }

// [#10 SK_FunctionConversion: 1]
func f(_ a: () -> Void) { print("() -> Void") }
// []
func f(_ a: () -> Float) { print("() -> Float") }
f { 10 } // => Float

実装PR: 探し中。情報求む。

() -> T型に() -> Neverとして推論されるクロージャー記法を書く場合(クロージャー記法のみ)

func f(_ a: () -> Void) { }
f { fatalError() }

// [#10 SK_FunctionConversion: 1]
func f(_ a: () -> Float) { print("() -> Float") }
// []
func f(_ a: () -> Never) { print("() -> Never") }
f { fatalError() } // => Never

実装PR: 探し中。情報求む。

タプル解除の暗黙変換をする場合

func f(_ a: ((Int, Int)) -> Void) {}

f { x, y in ... } // (Int, Int) -> Void

// [#10 SK_FunctionConversion: 1]
func f(_ a: ((Int, Int)) -> Void) {
    print("((Int, Int)) -> Void")
}
// []
func f(_ a: (Int, Int) -> Void) {
    print("(Int, Int) -> Void")
}

f { x, y in } // => (Int, Int) -> Void

実装PR: 探し中。情報求む。

representation (@convention(xxx)) が違う関数型に暗黙変換する場合

func f() -> @convention(swift) () -> Void {
    print("swift")
    return { }
}

let g: @convention(block) () -> Void = f()

// [#10 SK_FunctionConversion: 1]
func f() -> @convention(swift) () -> Void {
    print("swift")
    return { }
}

// []
func f() -> @convention(block) () -> Void {
    print("block")
    return {}
}

let g: @convention(block) () -> Void = f() // => block

引数に入れる場合は対象外らしい、なぜ?

func f(_ a: @convention(swift) () -> Void) {
    print("swift")
}

func f(_ a: @convention(block) () -> Void) -> Void {
    print("block")
}
f {} // 🔴 error: ambiguous use of 'f'
実装PR

GlobalActorの指定の有無に関する暗黙変換をする場合

func f(_ a: @MainActor () -> Void) {
}
f {}

// [#10 SK_FunctionConversion: 1]
func f(_ a: @MainActor () -> Void) { print("MainActor") }
// []
func f(_ a: () -> Void, _ b: Int = 10) { print("not MainActor") }
f {} // => not MainActor
実装PR

ランク11: SK_UserConversion

Objective-CやCの型に関連する暗黙変換に付くスコア。

メタタイプ等からAnyObjectへ暗黙変換する場合

class A {}

func f(_ a: AnyObject) {}
f(A.self)

class A {}

// [#11 SK_UserConversion: 1]
func f(_ a: AnyObject) { print("AnyObject") }
// [#7 SK_ValueToOptional: 1]
func f(_ a: A.Type?) { print("A.Type?") }

f(A.self) // => A.Type?
実装コミット

実装PR

AnyHashableへ暗黙変換する場合

struct B: Hashable {}

func f(_ a: AnyHashable) {}
f(B())

struct B: Hashable {}

// [#11 SK_UserConversion: 1]
func g(_ a: AnyHashable) { print("AnyHashable") }
// [#6 SK_EmptyExistentialConversion: 1]
func g(_ a: Any) { print("Any") }

let b: B = B()
g(b) // => Any
実装PR

Toll-Free Bridgeな型同士の暗黙変換をする場合

import Foundation
import CoreFoundation

func f(_ a: CFString) {}

let a: NSString = NSString()
f(a)

import Foundation
import CoreFoundation

// [#11 SK_UserConversion: 1]
func h(_ a: CFString) {}
// [#6 SK_EmptyExistentialConversion: 1]
func h(_ a: Any) {}

let a = NSString()
h(a) // => Any
実装コミット

ランク12: SK_ImplicitValueConversion

暗黙的にinitializerが呼ばれる際に付くスコア。

Double<->CGFloatの暗黙変換をする場合

func f(_ a: CGFloat) { }

let a: Double = 10.0
f(a)
func f(_ a: Double) { }

let a: CGFloat = 10.0
f(a)

import Foundation

// [#12 SK_ImplicitValueConversion: 30]
func f(_ a: CGFloat) { print("CGFloat") }
// [#6 SK_EmptyExistentialConversion: 1]
func f(_ a: Any) { print("Any") }

let a: Double = 10.0
f(a) // => Any
実装PR

SwiftのUnsafe[Mutable]Pointer<T>->Cの[const] U *で特定のT->Uで許される暗黙変換をする場合

Int[8, 16, 32, 64] -> UInt[8, 16, 32, 64]など

// UnsafePointer<UInt64>
void f(const u_int64_t * _Nonnull buffer) {}
let pointer = UnsafeMutablePointer<Int64>.allocate(capacity: 1)
pointer.initialize(to: 10)

// UnsafePointer<Int64> -> UnsafePointer<UInt64>
f(pointer)

pointer.deallocate()

// [#12 SK_ImplicitValueConversion: 1]
void f(const u_int64_t * _Nonnull buffer) {
    printf("C Pointer");
}
// [#7 SK_ValueToOptional: 1]
func f(_ a: UnsafePointer<Int64>?) { print("UnsafePointer<Int64>?") }

let pointer = UnsafeMutablePointer<Int64>.allocate(capacity: 1)
pointer.initialize(to: 10)

f(pointer) // => UnsafePointer<Int64>?

pointer.deallocate()
実装PR

ランク13: SK_ForceUnchecked

IUO(Implicitly Unwrapped Optional: T!)の暗黙アンラップに付くスコア。

func f(_ a: Int) {}

var a: Int! = 1
f(a)

// [#13 SK_ForceUnchecked: 1]
func f(_ a: Int) { print("Int") }
// []
func f(_ a: Int?) { print("Int?") }

var a: Int! = 1
f(a) // => Int?
実装PR

IUO実装当初のコミットで現在のロジックと異なる?

https://github.com/apple/swift/commit/e38a2b9a0820ec060e4c51c0f325d95234f4544b
https://github.com/apple/swift/commit/e3f6be8631f6a242e554fe521eaefa3b63551329

現在のincreaseScoreの実装PRはこっちだが変更意図がわからない、情報求む

https://github.com/apple/swift/pull/19203

ランク14: SK_UnresolvedMemberViaOptional

どの型のメンバを示しているか分からない記述(UnresolvedMemberExpr)がAA?の間にある場合、A側のオーバーロードに付くスコア。

struct A {
    init(_ a: Void) {}
}
let a: A? = .init(())

struct A {
    // [#14 SK_UnresolvedMemberViaOptional: 1, #7 SK_ValueToOptional: 1]
    init(_ a: Void) { print("A") }
}
extension Optional {
    // []
    init(_ a: Void) { 
        print("Optional")
        self = nil
    }
}
let b: A? = .init(()) // => Optional
実装PR

ランク15: SK_DisfavoredOverload

@_disfavoredOverloadによって付くスコア。

@_disfavoredOverload
func f(_ a: Int) { }

let a: Int = 1
f(a)

// [#15 SK_DisfavoredOverload: 1]
@_disfavoredOverload
func f(_ a: Int) { print("Int") }
// [#7 SK_ValueToOptional: 1]
func f(_ a: Int?) { print("Int?") }

let a: Int = 1
f(a) // => Int?
実装PR

ランク16: SK_ForwardTrailingClosure

関数呼び出しの際にTrailingClosureがforward scanで一致する場合に付くスコア。

func f(
    _ a: (() -> Void)? = nil,
    _ b: ((Int) -> Void)? = nil
) {}

f { } // f({}, nil)

func f(
    _ a: (() -> Void)? = nil,
    _ b: (() -> Void)? = nil
) {
    if a != nil { print("forward") }
    if b != nil { print("backward") }
}

// [#16, SK_ForwardTrailingClosure: 1]
// f({ }, nil)
// []
// f(nil, { })
f { } // => backward
実装PR

ランク17: SK_SyncInAsync

Asyncな関数内でSync関数を利用する場合に付くスコア。

async関数内から呼び出されているsyncなオーバーロードを呼び出す場合

func f() { }

func g() async {
    f()
}

// [#17 SK_SyncInAsync: 1]
func f() { }
// []
func f() async {}

func g() async {
    f() // 🔴 error: expression is 'async' but is not marked with 'await'
}
実装PR

syncな関数型からasyncな関数型の暗黙変換をする場合

func h(_ a: () async -> Void) {}
h { }

// [#17 SK_SyncInAsync: 1]
func h(_ a: () async -> Void) {
    print("async")
}
// []
func h(_ a: () -> Void) {
    print("sync")
}

h { } // => sync

async内の呼び出しで引数にUnappliedFunctionを入れるとSK_SyncInAsyncのポイントが追加される...?なぜ...?

// [#17 SK_SyncInAsync: 1]
func f(_ a: () -> Void) async {}

func hoge() async {
    func g() {}

    await f(g)
}
実装PR

ランク18: SK_AsyncInSyncMismatch

sync関数内の呼び出しではasyncなオーバーロードに付くスコア。

func f() async {}

func hoge() {
    f()
}

// [#18 SK_AsyncInSyncMismatch: 1]
func f() async { print("async") }
// []
func f() { print("sync") }

func hoge() {
    f() // => sync
}
実装PR

ランク19: SK_Unavailable

@available(*, unavailable)に付くスコア。エラーとなる。

@available(*, unavailable)のついたオーバーロードの場合

class A {
    @available(*, unavailable)
    func f() {}

    init() {}
}
A().f() // 🔴 error: 'f()' is unavailable

オーバーロードが存在するextension自体が@available(*, unavailable)な場合

struct A {}
protocol P { func f() } 

@available(*, unavailable)
extension A: P {
    func f() {
    }
}

A().f() // 🔴 error: 'f()' is unavailable
実装コミット

ランク20: SK_Hole

修正すべきだが、修正方法が分からない問題に対して付くスコア。

let a = p
Score: [component: applied fix(s), value: 1]
Type Variables:
  $T0 [allows bindings to: hole] [attributes: hole] [#defaultable_bindings: 1] [with possible bindings: <empty>] @ locator@0x12b19ac00 [Error@Source.swift:1:9]
Fixes:
  [fix: ignore invalid AST node] @ locator@0x12b19ac00 [Error@Source.swift:1:9]


---Solver statistics---
Total number of scopes explored: 1
Maximum depth reached while exploring solutions: 1
Time: 8.400000e-01ms
---Attempting to salvage and emit diagnostics---
  (attempting type variable binding $T0 := <<placeholder for $T0>>
    (increasing 'hole' score by 1 @ locator@0x12b19ac00 [Error@Source.swift:1:9])
    (Changes:
      (Newly Bound:
        > $T0 := <<placeholder for $T0>>
      )
    )
    (found solution: [component: applied fix(s), value: 1] [component: hole(s), value: 1])
  )

---Solution---
Fixed score: [component: applied fix(s), value: 1] [component: hole(s), value: 1]
Type variables:
  $T0 as <<placeholder for $T0>> @ locator@0x12b19ac00 [Error@Source.swift:1:9]
Defaulted constraints: locator@0x12b19ac00 [Error@Source.swift:1:9]
Fixes:
  [fix: ignore invalid AST node] @ locator@0x12b19ac00 [Error@Source.swift:1:9]
error: fatalError

aとpの型変数$T0が解決不可能かつ修正候補もないためHoleが付与されている。

実装PR

ランク21: SK_Fix

「もしかして」機能のために生成された仮説に付くスコア。

func f(a: Int) {}

let a: Int = 1
f(b: a)
// error: incorrect argument label in call (have 'b:', expected 'a:')
// f(b: a)
//   ^~
//   a

参考

https://www.swift.org/blog/new-diagnostic-arch-overview/

実装コミット

(おまけ)スコア規則以外の優先規則

オーバーロードは基本的にスコア規則に基づいて解決されますが、スコアが完全に同点になった場合はスコア規則以外の規則によって解を選択する必要があります。

https://github.com/apple/swift/blob/977bf6f7fb140e65129c4c82d898de84af47b661/docs/TypeChecker.md#comparing-solutions

When two solutions have the same score, the type variables and overload choices of the two systems are compared to produce a relative score:

  • If the two solutions have selected different type variable bindings for a type variable where a "more specific" type variable is a better match, and one of the type variable bindings is a subtype of the other, the solution with the subtype earns +1.
  • If an overload set has different selected overloads in the two solutions, the overloads are compared. If the type of the overload picked in one solution is a subtype of the type of the overload picked in the other solution, then first solution earns +1.
    The solution with the greater relative score is considered to be better than the other solution.

2つのソリューションが同じスコアの場合、2つのシステムの型変数とオーバーロードの選択が比較され、相対スコアが算出される:

  • 型変数バインディングの一方が他方のサブタイプである場合、2つの解が、「より具体的な」型変数がよりよくマッチする型変数に対して異なる型変数バインディングを選択し、サブタイプを持つ解は+1を獲得する。
  • オーバーロードに、2 つの解で選択されたオーバーロードが異なる場合、オーバーロードが比較される。一方の解で選択されたオーバーロードのタイプが、もう一方の解で選択されたオーバーロードのタイプのサブタイプである場合、最初の解が+1されます。
    相対スコアの大きい解が、他の解よりも優れているとみなされる。

直感的に説明すると、解の関係する型に継承関係がある場合、よりサブタイプである型が優先されます。

例1

class A {}
class B: A {}
class C: B {}

// []
func f(_ a: A) {
    print("A")
}
// []
func f(_ a: B) {
    print("B")
}

let c: C = C()
f(c) // => B

BはAのサブタイプであるので、Bが優先されます。

コンパイラの比較中のログ

comparing solutions 1 and 0
Comparing declarations
func f(_ a: B) {
  return
}
and
func f(_ a: A) {
  return
}
(isDynamicOverloadComparison: 0)
  (found solution: <default 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0>)
comparison result: better

Comparing declarations
func f(_ a: A) {
  return
}
and
func f(_ a: B) {
  return
}
(isDynamicOverloadComparison: 0)
(failed constraint A subtype B @ locator@0x13781fa00 [])
comparison result: not better
comparing solutions 1 and 0

例2

protocol A1 {}
protocol A2 {}
class B: A1, A2 {}

func f(_ a: any A1) {
    print("A1")
}
func f(_ a: any A2) {
    print("A2")
}

let b: B = B()
f(b)

A1とA2に継承関係がないので、優先規則によって解決できずambiguousとなります。

Source.swift:27:1: error: ambiguous use of 'f'
f(b)
^
Source.swift:19:6: note: found this candidate
func f(_ a: any A1) {
     ^
Source.swift:22:6: note: found this candidate
func f(_ a: any A2) {
     ^

最後に

スコア規則についてはある程度網羅的に紹介しましたが
おまけで触れたようなその他の優先規則については実際の実装のほんの一部に過ぎません。
興味のある方はぜひ実際のコードをご覧ください。

https://github.com/apple/swift/blob/895ac3ef96eba10a19488e6cba2bb1de3080e2de/lib/Sema/CSRanking.cpp#L947

Discussion