メインコンテンツへスキップ
#82501active

go/types: export Error.Code

新規提案

要約

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

概要

go/types の型エラー(types.Error)が内部に持っている「エラーの種類を表すコード」(MissingReturn や UnusedImport など)を公開 API にする提案です。目的は、ツールがエラーメッセージの文字列に頼らずにエラーの種類を判別できるようにすることです。

ステータス変更

(新規) → active
2026年11月12日、@aclements が proposal review group を代表して、この提案を active 列に追加しました。今後は毎週の proposal review meeting で審議されます。それまでの議論では、@adonovan(x/tools メンテナ)が「エラーコードは完璧な解決策ではないが、おそらく正しい方向だ」と前向きな意見を出していました。一方で、コードの安定性をどこまで保証するかという論点はまだ決着していません。審議に入る前提はそろったと判断されたものと考えられます。

技術的背景

現状の問題点

Go 1.16 以降、すべての types.Error は非公開フィールド go116code にエラーコードを持っています(CL 264179、issue #42290 で導入)。導入時に非公開とされた理由は2つです。約145個と API の規模が大きいこと、そして初期には値が変わりそうだったことです。
その後6年が経ち、internal/types/errors の各コードにはドキュメントと例が付き、テストで検証されています。ファイルにも「既存の値は変更せず、新しいコードは末尾に追加する」と明記されています。それでもツールがこの値を読むにはリフレクションしかありません。x/tools は typesinternal.ErrorCodeStartEnd とコードの独自コピーを持っており、gopls の fillreturns、nonewvars、noresultvalues などの analyzer がこれに依存しています。x/tools 以外のツールは、Go のリリースごとに壊れるかもしれないリフレクションを避けて、エラーメッセージの文字列照合に頼るのが実情です。しかしメッセージも安定性は保証されていません。

提案された解決策

type Error struct {
	Fset *token.FileSet
	Pos  token.Pos
	Msg  string
	Soft bool
	Code ErrorCode // エラーの種類。不明なら 0
}
// ErrorCode は型チェックエラーの種類を表す。
// コードは安定しており、新規追加のみで既存の値は変わらない。
type ErrorCode int
func (c ErrorCode) String() string
const (
	BlankPkgName      ErrorCode = 2
	MismatchedPkgName ErrorCode = 3
	// ... internal/types/errors の残り全て
)

types パッケージに約145個の名前が加わるのを避けたい場合は、代替案として go/types/errorcode のような新パッケージに定数を置く方法も示されています。この提案は、Error.Start/End を公開する #71803 と対になるものです。

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

  • 意図的に改変した構文の型チェック: cue get go は、型チェックを速くするために関数本体を取り除いてから型チェックします(packages.Config.ParseFile のドキュメントや #34806 で推奨されている手法)。その結果出る「missing return」と「imported and not used」は、現在メッセージ照合で除外しています。そのため go/types が文言を変えると、偽のエラーを報告して失敗します。
  • gopls のような修正提案ツール: リフレクションに頼らず、公開 API だけでエラーの種類に応じた quick fix を実装できます。
  • サードパーティの linter や IDE 連携: 特定の種類のエラーだけを抑制したり、分類して表示したりできます。

コード例

// Before: メッセージの文字列照合(文言が変わると壊れる)
for _, err := range pkg.TypeErrors {
	if strings.Contains(err.Msg, "missing return") ||
		strings.Contains(err.Msg, "imported and not used") {
		continue
	}
	report(err)
}
// After: エラーコードで判定
for _, err := range pkg.TypeErrors {
	if err.Code == types.MissingReturn || err.Code == types.UnusedImport {
		continue
	}
	report(err)
}

議論のハイライト

  • go/packages への波及: @mvdan は packages.Error にも Code types.ErrorCode を追加する案を示しました。多くのツールが使う Package.Errors は TypeErrors と対応付けられていないためです。追加されれば slices.DeleteFunc で簡単に除外できます。この提案に含めるか、x/tools の後続提案にするかは未定です。
  • エラーコード方式の限界: @adonovan は、カテゴリは分割や統合によって変化するし、似たエラーにどのコードを使うべきかが曖昧なこともあると指摘しました。それでも enum と文字列配列にすぎないので、go/types に追加すること自体に問題はないとしています。
  • 安定性保証が最大の論点: @mrkfrmn は、出力されるコードもメッセージと同じく時間とともに変わりうるので、安定性の保証は難しいと述べました。公開を「使い勝手の改善」と位置付けるならよいが、安定した性質とするのは別の話だという立場です。
  • mvdan の反論: Go ツールは2つの Go バージョンをサポートするため、Go 1.27 でビルドした cue が Go 1.26 環境で使われることもあります。コードがリリースごとに変わるなら、strings.Contains を == に置き換えるだけの意味しかありません。mvdan は Linux の syscall エラーコードのような追記型を例に挙げ、言語変更、バグ、型チェッカーの再設計のような場合を除いてコードは変えない、という合意を求めています。

関連リンク