メインコンテンツへスキップ

Go Proposal Weekly Digest

Go言語のproposal更新を毎週お届け

#79603accepted

go/types: NewType{Param,}List

ステータス変更: likely_accept accepted

要約

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

概要

go/types パッケージの TypeParamList および TypeList は読み取り専用のAPIしか提供しておらず、外部からこれらを新規作成する手段がありませんでした。このproposalは、NewTypeParamListNewTypeList という2つのコンストラクタ関数を追加することで、ツール開発者が安全にこれらの型を構築できるようにすることを目的としています。

ステータス変更

likely_acceptaccepted
2026年5月28日に @aclements によって likely accept と判定された後、6月3日の提案レビューグループの確認において反対意見や追加議論なく、そのまま accepted に移行しました。提案内容がシンプルで影響範囲が限定的であり、既存の unsafe なワークアラウンドを排除できる明確な実益があることから、速やかに受理されました。

技術的背景

現状の問題点

go/typesTypeParamList は内部的に tparams []*TypeParam フィールドを持つ構造体ですが、エクスポートされたコンストラクタが存在しません。同様に TypeList にも外部から使える NewTypeList がありませんでした。
ジェネリックメソッド(#77273)のサポートを進める過程で、レシーバのtype parameterリストとメソッドのtype parameterリストを結合して単一の TypeParamList として扱いたいユースケースが生じました。しかし公式APIがないため、開発者は次のような問題のあるコードを書かざるを得ない状況でした。
go/ssax/tools)での実際のワークアラウンド:

// unsafe.Pointerを使った危険なキャスト(現状のx/tools/go/ssa)
func consTypeParamLists(l, r *types.TypeParamList) *types.TypeParamList {
    tpars := make([]*types.TypeParam, l.Len()+r.Len())
    for i := range l.Len() {
        tpars[i] = l.At(i)
    }
    for i := range r.Len() {
        tpars[i+l.Len()] = r.At(i)
    }
    return (*types.TypeParamList)(unsafe.Pointer(&tpars))
}

このコードは TypeParamList の内部レイアウトに依存しており、Goの将来のバージョンで内部構造が変わった場合に動作しなくなる危険があります。
cmd/compilenoder でも同様の問題があり、TypeParamList[]*TypeParam スライスに変換してから操作するという迂回策を取っていました。

提案された解決策

以下の2つのコンストラクタ関数を go/types パッケージに追加します:

func NewTypeParamList(tparams ...*TypeParam) *TypeParamList
func NewTypeList(types ...*Type) *TypeList

これにより、TypeParamListTypeList を公開APIとして安全に構築できるようになります。NewTypeList の追加は @adonovan(Googleのツールズチームメンバー)がレビュー中に提案し、対称性の観点からセットで採用されました。

これによって何ができるようになるか

ジェネリック関連のツール開発において、TypeParamListTypeList を自前で構築する必要があるケースに対して、安全で明示的なAPIが提供されます。

コード例

// Before: unsafe.Pointerを使った危険な回避策
func mergeTypeParams(l, r *types.TypeParamList) *types.TypeParamList {
    tpars := make([]*types.TypeParam, l.Len()+r.Len())
    for i := range l.Len() {
        tpars[i] = l.At(i)
    }
    for i := range r.Len() {
        tpars[i+l.Len()] = r.At(i)
    }
    // 内部構造に依存したunsafeなキャスト
    return (*types.TypeParamList)(unsafe.Pointer(&tpars))
}
// After: 新APIを使った安全な書き方
func mergeTypeParams(l, r *types.TypeParamList) *types.TypeParamList {
    tpars := make([]*types.TypeParam, l.Len()+r.Len())
    for i := range l.Len() {
        tpars[i] = l.At(i)
    }
    for i := range r.Len() {
        tpars[i+l.Len()] = r.At(i)
    }
    return types.NewTypeParamList(tpars...)
}

議論のハイライト

  • NewTypeList の追加はレビュー中に提案: Issue本文は NewTypeParamList のみを提案していましたが、@adonovan が対称性の観点から NewTypeList も同時に追加すべきと提案し、最終的な承認内容に両方が含まれました。
  • ジェネリックメソッド(#77273)の実装を後押し: レシーバのtype parameterとメソッドのtype parameterを結合したリストを扱うユースケースが、このproposalの主な動機の一つであり、ジェネリックメソッドのサポート作業と連動しています。
  • unsafe なワークアラウンドの排除が主目的: x/tools の go/ssa において、型の内部構造に依存した unsafe.Pointer キャストが実際に行われており、このproposalはその廃止に直接貢献します。
  • シンプルさゆえに迅速な承認: 提案されたAPIは非常にシンプルで、既存APIとの後方互換性も完全に保たれます。反対意見や代替案の議論がなく、likely accept から1週間で accepted に移行しました。
  • cmd/compile 内部の noder も恩恵を受ける: コンパイラ内部でも TypeParamList[]*TypeParam に変換して操作するという迂回策が存在しており、このAPIが追加されることで内部コードも整理される見込みです。

関連リンク