Swiftのオーバーロード選択のスコア規則21種類
はじめに
この記事は、2019年(当時はSwift5.1.2)にomochimetaruさんがわいわいswiftc#16にて発表した Swiftのオーバーロード選択のスコア規則12種類 のSwift5.10版です。
当時のスコア規則は12種類でしたが、Swift5.10では9種類増えた21種類となっています。
オーバーロード選択のスコア規則とは
オーバーロードと型推論
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)
についているスコアのスコアランクの方が低いため、解となります。
スコア一覧
定義
ランク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
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]
実装コミット
(当初はString, Array, inoutで別々のスコアだった)
ランク5: SK_KeyPathSubscript
KeyPath
のsubscript
関連で付くスコア。
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
@dynamicMemberLookup
のsubscript(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: 探し中。情報求む。
@convention(xxx)
) が違う関数型に暗黙変換する場合
representation (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
Unsafe[Mutable]Pointer<T>
->Cの[const] U *
で特定のT
->U
で許される暗黙変換をする場合
Swiftの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
実装当初のコミットで現在のロジックと異なる?
現在のincreaseScore
の実装PRはこっちだが変更意図がわからない、情報求む
ランク14: SK_UnresolvedMemberViaOptional
どの型のメンバを示しているか分からない記述(UnresolvedMemberExpr
)がA
とA?
の間にある場合、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
@available(*, unavailable)
な場合
オーバーロードが存在するextension自体が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
参考
実装コミット
(おまけ)スコア規則以外の優先規則
オーバーロードは基本的にスコア規則に基づいて解決されますが、スコアが完全に同点になった場合はスコア規則以外の規則によって解を選択する必要があります。
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) {
^
最後に
スコア規則についてはある程度網羅的に紹介しましたが
おまけで触れたようなその他の優先規則については実際の実装のほんの一部に過ぎません。
興味のある方はぜひ実際のコードをご覧ください。
Discussion