slidenumber: true autoscale: true
- 新米Swiftコミッター
-
(自分は)オンライン発表を聞くのは集中度が下がる😐
-
話す側からしても、リアクションが少なめで微妙🤨
-
手を動かしてもらいます💡
-
ターミナルとエディタを開いておいてね💻
New Diagnostic Architecture Overview
https://swift.org/blog/new-diagnostic-arch-overview/
-
言語機能ではないのであまり注目されていない(と思う)
-
だけど、コンパイラの機能としては強力!
func f(aa: Int, bb: Int) {}
func main() {
// ❌ `bb:` を付け忘れている
f(aa: 1, 2)
}
$ alias swift515='xcrun --toolchain org.swift.515120200323a swift'
$ swift515 a.swift
a.swift:4:6: error: missing argument label 'bb:' in call
f(aa: 1, 2)
^
bb:
😀
func f(aa: Int, bb: Int) {}
// fのオーバーロードが追加されている
func f(cc: Int, dd: Int) {}
func main() {
// ❌ `bb:` を付け忘れている
f(aa: 1, 2)
}
$ swift515 a.swift
a.swift:6:5: error: argument labels '(aa:, _:)' do not match any available overloads
f(aa: 1, 2)
^~~~~~~~~~~
a.swift:6:5: note: overloads for 'f' exist with these partially matching parameter
lists: (aa: Int, bb: Int), (cc: Int, dd: Int)
f(aa: 1, 2)
^
😩
$ swift a.swift
$ swift a.swift
a.swift:6:6: error: missing argument label 'bb:' in call
f(aa: 1, 2)
^
bb:
🤩
- コンパイラが、ユーザがどっちのオーバーロードを呼ぶつもりだったのか、予測している
- 型推論においてオーバーロードはDisjunction Constraintとして扱われる
<disjunction> {
$T1 <bind OL> (aa: Int, bb: Int) -> Void
$T1 <bind OL> (cc: Int, dd: Int) -> Void
}
$T1 <appfn> (aa: Int, _: Int) -> Void
-
候補制約を一つずつ推論器に試しに投入する
-
全部失敗 → エラー: マッチするオーバーロードが無い
-
一つ成功 → それを型推論結果として採用
-
複数成功 → 成功した解同士を比較して、最良のものを選択
-
比較結果が同率一位 → エラー: 曖昧なコード
func f(_ a: Int?) { print("Int?") }
func f(_ a: Any) { print("Any") }
func main() {
// 1 は Int? でもあるし、 Any でもある。
f(1)
}
main()
$ swift b.swift
$ swift b.swift
Any
$ swift -Xfrontend -debug-constraints b.swift
Comparing 2 viable solutions
--- Solution #0 ---
Fixed score: 0 0 0 0 0 0 0 0 1 0 0 0
Type variables:
$T0 as (Int?) -> () @ locator@0x7ff8a0808800 [OverloadedDeclRef@b.swift:6:5]
--- Solution #1 ---
Fixed score: 0 0 0 0 0 0 0 0 0 1 0 0
Type variables:
$T0 as (Any) -> () @ locator@0x7ff8a0808800 [OverloadedDeclRef@b.swift:6:5]
0 0 0 0 0 0 0 0 1 0 0 0
^ SK_ValueToOptional は Level4
0 0 0 0 0 0 0 0 0 1 0 0
^ SK_EmptyExistentialConversion は Level3
- Anyへの変換の方が安い
Swiftのオーバーロード選択のスコア規則12種類
https://speakerdeck.com/omochi/swiftfalseobarodoxuan-ze-falsesukoagui-ze-12zhong-lei
-
間違いがあったが修復した、というスコア(
SK_Fix
)を定義する -
関数呼び出しでラベルに間違いがあっても、修復スコアを積んで処理をすすめる
-
最終的に修復スコアの付いた解が選択された場合、 コンパイルエラーにしつつ、その解に対する診断を出力する
// (都合上、定義順を入れ替え)
func f(cc: Int, dd: Int) {}
func f(aa: Int, bb: Int) {}
func main() {
f(aa: 1, 2)
}
$ swift -Xfrontend -debug-constraints a.swift
$ swift -Xfrontend -debug-constraints a.swift
--- Solution #0 ---
Fixed score: 4 0 0 0 0 0 0 0 0 0 0 0
Fixes:
[fix: re-label argument(s)] @ locator@0x7ff8e08cf100 [Call@a.swift:4:5 -> apply argument]
--- Solution #1 ---
Fixed score: 1 0 0 0 0 0 0 0 0 0 0 0
Fixes:
[fix: re-label argument(s)] @ locator@0x7ff8e08cf100 [Call@a.swift:4:5 -> apply argument]
Fixed score: 4 0 0 0 0 0 0 0 0 0 0 0
^ Level12のSK_Fixが4点 =
ラベルエラーの基本点が1点 +
aa: → cc: のwrongが3点 +
_: → dd: のmissingが0点
Fixed score: 1 0 0 0 0 0 0 0 0 0 0 0
^ Level12のSK_Fixが1点 =
ラベルエラーの基本点が1点 +
_: → bb: のmissingが0点
(aa:_:)
からの修復は(aa:bb:)
の方が(cc:dd:)
より安い
func f(aa: String) {}
func f(bb: Int) {}
func main() {
// ラベルの間違い具合は同率だけど・・・
f(cc: 1)
}
$ swift c.swift
c.swift:5:6: error: incorrect argument label in call (have 'cc:', expected 'bb:')
f(cc: 1)
^~~
bb
(bb: Int)
の方を呼びたかっただろうと判断している
--- Solution #0 ---
Fixed score: 6 0 0 0 0 0 0 0 0 0 0 0
Fixes:
[fix: re-label argument(s)] @ locator@0x7fc7638cedf8 [Call@c.swift:5:5 -> apply argument]
[fix: allow argument to parameter type conversion mismatch] @ locator@0x7fc7638cee48
[Call@c.swift:5:5 -> apply argument -> comparing call argument #0 to parameter #0]
--- Solution #1 ---
Fixed score: 4 0 0 0 0 0 0 0 0 0 0 0
Fixes:
[fix: re-label argument(s)] @ locator@0x7fc7638cedf8 [Call@c.swift:5:5 -> apply argument]
(cc: 1) to (aa: String)
は ラベルエラー基本点1点 + cc:→aa: wrong 3点 + 型エラー2点 で合計6点(cc: 1) to (bb: Int)
は ラベルエラー基本点1点 + cc:→bb: wrong 3点 で合計4点でこちらが安い
-
Swift5.2では、エラーを修復しながら型推論を進めるCSFix機構が入ったことで、 オーバーロードが関わるコンパイルエラーがより親切になった
-
その実現には、複数の推論解を優先度付けする既存の仕組みをうまく活用した
-
関数呼び出しにおいて、呼び出し側の実引数と、 関数側の仮引数の対応関係を決める処理を ラベルマッチングと呼ぶっぽい (コミットメッセージとか見てると)
-
Swiftのこのあたりの言語仕様はかなり複雑
-
修復する場合も考えるとさらに複雑になる
func f(aa: Int, bb: Int, cc: Int) {}
f(aa: 1, bb: 2, cc: 3)
func f(aa: Int, bb: Int, cc: Int) {}
f(aax: 1, bb: 2, cc: 3)
// incorrect argument label in call (have 'aax:bb:cc:', expected 'aa:bb:cc:')
f(bb: 2, cc: 3, aa: 1)
// argument 'aa' must precede argument 'bb'
func f(_ aa: Int, _ bb: Int, cc: Int) {}
f(1, 2, cc: 3)
func f(_ aa: Int, _ bb: Int, cc: Int) {}
f(1, 2, 3)
// missing argument label 'cc:' in call
f(1, bb: 2, cc: 3)
// extraneous argument label 'bb:' in call
func f(aa: Int, bb: Int, cc: Int) {}
f(aa: 1, bb: 2, cc: 3, dd: 4)
// extra argument 'dd' in call
f(aa: 1, bb: 2)
// missing argument for parameter 'cc' in call
func f(_ aa: Int, bb: Int, _ cc: Int) {}
f(bb: 2, 3)
// missing argument for parameter #1 in call
bb:
を基準に3
を_ cc:
にマッチ、_ aa:
が不足と判断
func f(aa: Int, bb: Int..., cc: Int) {}
f(aa: 1, bb: 21, 22, cc: 3)
f(aa: 1, cc: 3)
func f(aa: Int, bb: Int..., _ cc: Int) {}
// a parameter following a variadic parameter requires a label
-
可変長引数の後続はラベルが必須
-
以下の場合に
3
が曖昧になるからだろう
f(aa: 1, bb: 2, 3)
func f(aa: Int..., bb: Int...) {}
// only a single variadic parameter '...' is permitted
-
この制限を外す提案がある
-
Lifting the 1 variadic param per function restriction
https://forums.swift.org/t/lifting-the-1-variadic-param-per-function-restriction/33787
func f(aa: Int, bb: Int = 0, _ cc: Int) {}
f(aa: 1, bb: 2, 3)
f(aa: 1, 3)
func f(aa: Int, _ bb: Int = 0, _ cc: Int) {}
f(aa: 1, 2, 3)
// OK
f(aa: 1, 3)
func f(aa: Int, _ bb: Int = 0, _ cc: Int) {}
f(aa: 1, 2, 3)
// OK
f(aa: 1, 3)
// missing argument for parameter #3 in call
-
デフォルト付きラベル無し引数に、ラベル無し引数が続くと、 このデフォルト値が使われる事はない
-
後続のラベル無し引数だけ渡したくても、 手前のデフォルト付き引数に吸い込まれる
func f(aa: Int, bb: Int = 0, bb cc: Int) {}
f(aa: 1, bb: 2, bb: 3)
// OK
f(aa: 1, bb: 3)
func f(aa: Int, bb: Int = 0, bb cc: Int) {}
f(aa: 1, bb: 2, bb: 3)
// OK
f(aa: 1, bb: 3)
// missing argument for parameter 'bb' in call
- 同じラベルを使う事ができるので、 デフォルト付きラベル付き引数に、同じラベル引数が続くと、 やはり使われないデフォルト値が生じる
func f(aa: Int, bb: Int..., cc: Int = 0, _ dd: Int) {}
f(aa: 1, bb: 2, cc: 3, 4)
// OK
f(aa: 1, bb: 2, 4)
// missing argument for parameter #4 in call
-
可変長引数の後にデフォルト付き引数を置いて、 さらにその後にラベル無し引数を置くと、 機能しないデフォルト値が生じる
-
デフォルト付き引数を省略した場合、 可変長引数の後続の値も可変長引数に吸い込まれるため、 ラベル無し引数に値を与える事ができない
func f(aa: Int, bb: Int, cc: () -> Void) {}
f(aa: 1, bb: 2) { }
func f(aa: Int, bb: Int, cc: Int) {}
f(aa: 1, bb: 2) { }
// trailing closure passed to parameter of type 'Int' that does not accept a closure
func f<T>(type: T.Type, aa: Int, bb: Int, cc: T) {}
f(type: (() -> Void).self, aa: 1, bb: 2) { }
func f<T>(type: T.Type, aa: Int, bb: Int, cc: T) {}
f(type: Int.self, aa: 1, bb: 2) { }
// cannot convert value of type '() -> ()' to expected argument type 'Int'
- 末尾クロージャ自体は許容されてしまって、その先で型エラーになる
func f(aa: Int, bb: () -> Void, cc: Int = 0) {}
f(aa: 1) { }
func f(aa: Int, bb: () -> Void, cc: Int = 0) {}
f(aa: 1) { }
- 最近できるようになった
func f(aa: Int, bb: () -> Void, cc: Int = 0) {}
f(aa: 1, cc: 3) { }
func f(aa: Int, bb: () -> Void, cc: Int = 0) {}
f(aa: 1, cc: 3) { }
// OK
- バグ?
func f(aa: Int, bb: () -> Void, cc: Int = 0, dd: Int = 0) {}
f(aa: 1) { }
func f(aa: Int, bb: () -> Void, cc: Int = 0, dd: Int = 0) {}
f(aa: 1) { }
// trailing closure passed to parameter of type 'Int' that does not accept a closure
// missing argument for parameter 'bb' in call
-
できない(なんでや)
-
僕がPRを出してマージされ、実装された
https://github.com/apple/swift/pull/29845
-
しかし、いろいろあってrevertされた😔
https://github.com/apple/swift/pull/30656
func f(aa: Int, bb: Int, cc: () -> Void...) {}
f(aa: 1, bb: 2) {}
// OK
f(aa: 1, bb: 2, cc: {}, {})
// OK
f(aa: 1, bb: 2, cc: {}) {}
func f(aa: Int, bb: Int, cc: () -> Void...) {}
f(aa: 1, bb: 2) {}
// OK
f(aa: 1, bb: 2, cc: {}, {})
// OK
f(aa: 1, bb: 2, cc: {}) {}
// extra argument 'cc' in call
-
末尾クロージャのマッチがされると可変長機能が効かない
-
バグ?
func f(aa: Int, bb: Int, cc: Int) {}
f(aa: 1, dd: 4, bbx: 2, cc: 3)
// extra argument 'dd' in call
f(aa: 1, dd: 4, ee: 5, cc: 3)
// extra arguments at positions #2, #3 in call
// missing argument for parameter 'bb' in call
-
編集距離がある条件を満たす時、typo扱いする
-
1件目は
bbx:
がbb:
のtypoだと判定されているので、2件目とは異なるエラーになる
UIView.animate(withDuration: 0.3) {
self.view.alpha = 0
} completion: { _ in
self.view.removeFromSuperview()
}
-
最近AcceptされたSE-0279
-
さらに複雑になったね!😇
func f(_ aa: Int, _ bb: Int) {}
f(1)
// missing argument for parameter #2 in call
- これは普通
func f(_ aa: String, _ bb: Int) {}
f(1)
func f(_ aa: String, _ bb: Int) {}
f(1)
// missing argument for parameter #1 in call
-
引数の
1
が_ bb:
に渡された事になっている -
マッチングで型を見ている?
-
マッチングは通常通りやっている
-
診断フェーズで後から解釈変更している (型エラーなど他のFixスコアと矛盾した振る舞いを生じうる・・・)
-
[Diagnostics] Port missing argument(s) diagnostics #27362
https://github.com/apple/swift/pull/27362
func f(_ aa: String, _ bb: Int, cc: Int) {}
f(1, cc: 3)
func f(_ aa: String, _ bb: Int, cc: Int) {}
f(1, cc: 3)
// missing argument for parameter #2 in call
// cannot convert value of type 'Int' to expected argument type 'String'
- 3引数だと発動しない(なんでや)
func f(aa: String, bb: Int) {}
f(aa: 1)
func f(aa: String, bb: Int) {}
f(aa: 1)
// missing argument for parameter 'aa' in call
-
ラベルがあっても発動する
-
意味不明な振る舞いになる、バグ?
func f(aa: String = "", bb: Int) {}
f(aa: 1)
func f(aa: String = "", bb: Int) {}
f(aa: 1)
// missing argument for parameter 'aa' in call
- デフォルト引数を与える事で完全に意味不明に
-
複雑なので、仕様が組み合わさる部分などで怪しい挙動があったりする
-
クラッシュバグもあった(ワシが直した)
https://github.com/apple/swift/pull/30348
-
SE-0279は新しい爆弾かも💣
-
Swift5.2では、エラーを修復しながら型推論を進めるCSFix機構が入ったことで、 オーバーロードが関わるコンパイルエラーがより親切になった
-
その実現には、複数の推論解を優先度付けする既存の仕組みをうまく活用した
-
ラベルマッチはとても複雑