メインコンテンツへスキップ
#60994likely_accept

go/types: add func CommonUnderlying(Type) Type, (formerly CoreType)

ステータス変更: active likely_accept

要約

AIによる要約であり、誤りを含む場合があります。

概要

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(TypeSet API)を立てました。両者は独立して決められるものとして切り分けられています。
  • 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年半後に改訂版として再始動しました。

関連リンク