ジェネリック関数の型推論が、複合リテラルやチャネル送信、型変換など「代入コンテキスト」全般で一貫して機能するようになった
Changes to the language
概要
ジェネリック関数の型推論が、代入可能性(assignability)が問われるすべての文脈で一貫して機能するようになった。これまでは通常の代入文(s.f = g)では型引数を省略できる一方、構造体・配列・スライス・マップの複合リテラル内の要素(s = S{f: g})やチャネル送信(c <- g)、型変換では明示的なインスタンス化(g[int])が必要だった。左辺の型から型引数が一意に推論できる場面ではこの不整合が解消され、複合リテラルやチャネル送信でも明示的な型引数指定を省略できる。
導入経緯
発端は Robert Griesemer 氏によるバグ報告 で、s.f = g は動作するのに s = S{f: g} はエラーになる非一貫性を指摘するものだった。しかし仕様の Instantiations セクションが型推論の適用範囲を明示的に列挙していたため、現状の挙動自体は仕様通りであることが判明し、バグ修正ではなく言語プロポーザルとして扱われることになった。
当初は仕様に新たに「代入コンテキスト(assignment context)」という概念を導入する案が検討されたが、Griesemer 氏の調査により、x が代入コンテキストに置かれる箇所は既存の「代入可能性(assignability)」の定義でカバーできることが判明し、代入可能性の定義に(部分的に)インスタンス化されていないジェネリック関数の代入ルールを追加するという、より変更範囲の小さいアプローチが採用された。具体的な仕様変更文言は CL 751312 にまとめられている。
議論タイムライン
- 2026-02-09: Proposal Review Meeting で議論され、active に。
- 2026-03-11: 週次レビュー会議で likely accept と判定。
- 2026-03-18: 前週から議論に変更なく accepted として正式採択。
議論のハイライト
- 当初はバグとして報告されたが、仕様が型推論の適用範囲を明示的に限定していることが判明し、言語プロポーザルとして再定義された。
- 「代入コンテキスト」を仕様に新概念として導入する案は採らず、既存の「代入可能性」の定義を拡張するアプローチに落ち着いた。これにより複合リテラル・チャネル送信・関数引数・return文・型変換など、代入可能性が問われる箇所すべてが自動的にカバーされる。
- Griesemer 氏は、型変換
T(x)におけるxも「代入可能であれば良い」という点で代入コンテキストに含まれることを指摘している。 - レビューでは
go/typesAPI 側にもこの概念を統一的に公開する余地があるという意見が出た。 - この変更が、別の承認済み提案(#70638)の実装を後押しする可能性があるという指摘もあった。
使用例
Before
package main
type F func(int)
type M map[string]F
func g[T any](T) {}
func main() {
// 複合リテラル内では型引数の明示的な指定が必要だった
m := M{"foo": g[int]}
_ = m
}
After
package main
type F func(int)
type M map[string]F
func g[T any](T) {}
func main() {
// 左辺の型から型引数 int が推論されるため省略できる
m := M{"foo": g}
_ = m
}
移行時の注意
既存コードで書かれた明示的な型引数指定(g[int])は今後も引き続き有効であり、書き換えは不要。今回の変更は省略可能なケースが増えるだけで、既存コードの挙動を変えるものではない。
実装解説
go/types(および cmd/compile/internal/types2)では、代入先の型を T として渡す check.assignment が、内部で check.nonGeneric(newTarget(T, context), x) を呼び出し、x が型引数未指定のジェネリック関数であれば T をターゲットにした型推論(funcInst)を試みる(src/go/types/assignments.go;l=97、nonGeneric の定義は src/go/types/expr.go;l=999)。
複合リテラルの要素は、まず check.genericExpr でジェネリック関数のまま(型引数を確定させずに)評価し、そのうえで要素の対象型を渡して check.assignment を呼ぶことで推論を効かせている(構造体リテラルでの例: src/go/types/literals.go;l=177 と l=203)。チャネル送信も同様に、送信値を check.genericExpr で評価したあと、チャネルの要素型を対象として check.assignment を呼ぶ構造になっている(src/go/types/stmt.go;l=464-472)。この共通の経路により、代入可能性が問われるすべての箇所で自動的に型推論が機能するようになっている。