go/types: add func CommonUnderlying(Type) Type, (formerly CoreType)
要約
概要
go/types パッケージに、型パラメータの型集合(type set)に含まれる型が共通して持つ underlying type(基底型)を返す関数 CommonUnderlying(Type) Type を公開APIとして追加する提案です。元は「Core type(コア型)」を求める CoreType 関数として提案されていましたが、改名されました。
ステータス変更
active → likely_accept
提案レビューグループで担当者が話し合うよう指示され、2026年11月11日に @adonovan、@griesemer、@mrkfrmn の3人で議論しました。3人とも「この内容で問題ない(happy with this)」という結論で、翌日 @aclements が likely accept を宣言しました。改訂版の仕様は @adonovan が2026年10月22日のコメントで出しています。
技術的背景
現状の問題点
Go 1.18 で型パラメータが入ったとき、仕様(spec)に「コア型」という概念が加わりました。ところが go/types は、コア型を求める処理を非公開の coreType() として持つだけでした。そのため x/tools(internal/typeparams.CoreType)や GopherJS などのツールが、同じ処理をそれぞれ再実装してきました。
2025年に #70128(spec からコア型の概念を削除)が承認されると、この提案は役目を終えたとして一度クローズされました。しかし @adonovan によると、x/tools にはいまも CoreType の呼び出しが52箇所あり、どれも正しく、型集合API(#61013)に置き換えるほうが適切な箇所もありません。spec から名前が消えても、実務上の概念としては残っていたわけです。
提案された解決策
// CommonUnderlying returns the common underlying type of the types in
// the type set implied by t, or nil if there is none.
func CommonUnderlying(t Type) Type
tが型パラメータでなければt.Underlying()を返します。インターフェース型の値は、インターフェースの型集合ではなく、単一の型tとして扱われます。tが型パラメータなら、制約の型集合に含まれるすべての specific type(具体的な型)が共有する、ただ1つの underlying type を返します。anyやcomparableのように specific type がない場合や、underlying type が一致しない場合はnilを返します。- 特例として、すべてがチャネル型で、要素型が同一かつ方向が矛盾しない場合は、最も制限の強いチャネル型を返します(例:
chan intと<-chan intからは<-chan int)。
これによって何ができるようになるか
- 静的解析ツール(analyzer):
T ~*Sのような型パラメータを「ポインタとして扱えるか」などを、独自実装なしで判定できます。 - コンパイラや変換系(GopherJS など): ジェネリックなコードでの range、make、チャネル操作などについて、どう扱うかを決めやすくなります。
- x/tools などのエコシステム: 内部にコピーしているロジックを削除でき、spec の変更に合わせた保守を
go/types側に任せられます。
コード例
// Before: x/tools 内部パッケージなどの再実装に頼るしかなかった
import "golang.org/x/tools/internal/typeparams" // 外部からは import できない
if ptr, ok := typeparams.CoreType(t).(*types.Pointer); ok {
elem := ptr.Elem()
// ...
}
// After: 標準の go/types だけで書ける
if ptr, ok := types.CommonUnderlying(t).(*types.Pointer); ok {
elem := ptr.Elem()
// ...
}
// 例: func F[T ~[]int](x T) の T → []int
// func G[T any](x T) の T → nil
議論のハイライト
- 型集合APIとの関係: @findleyr は当初「解析ツールには、型集合の正規化表現(
NormalTerms相当)も必要」と指摘し、別の提案 #61013(TypeSetAPI)を立てました。両者は独立して決められるものとして切り分けられています。 nilを返す設計:Underlyingと違ってnilを返すことがあるので、(Type, bool)を返して呼び出し側に確認を強いるべきかが議論されました。@timothy-king が「bool はnilと同じ意味で冗長」と述べ、nilを返す形に落ち着いています。実際の利用箇所のほとんどは直後に型アサーションをしているため、nilでも自然に扱えます。- bytestring の扱い: 旧 spec では、
[]byte | stringの集合が特別に「bytestring」というコア型を持つとされ、その表現方法が論点になっていました。新しい仕様では specific type が underlying type を共有することを条件にしており、チャネルだけが特例です。bytestring の特例は仕様に含まれていないと読めます。 - 名前の変更: spec から「コア型」という用語が消えたため(#70128)、
CoreTypeではなく、何を返すかがそのまま分かるCommonUnderlyingに改名されました。 - 再オープンの経緯: 一度は不要としてクローズされましたが、実際のコードでの使われ方が根拠となり、約1年半後に改訂版として再始動しました。