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

Go Proposal Weekly Digest

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

#79802accepted

go/parser: add deprecated func ResolveFile(\\*File)

ステータス変更: likely_accept accepted

要約

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

概要

go/parserパッケージに、識別子とオブジェクトの対応付け(レガシーなオブジェクト解決)を後付けで実行できる新関数ResolveFile(*ast.File)を追加する提案です。SkipObjectResolutionモードでパースした構文木に対しても、必要な場合だけ明示的に解決処理を適用できるようにします。

ステータス変更

likely_acceptaccepted
2026年6月18日にアクティブ提案としてレビューが開始され、6月25日の会議で「likely accept」と判定されました。その後、実装CL(CL 794420)が提出され、7月8日の定例レビュー会議で「no change in consensus(意見の変化なし)」として正式にacceptedとなりました。反対意見が出なかったため、通常の1週間の猶予期間を経て速やかに承認されています。

技術的背景

現状の問題点

Goの構文解析結果を表すast.Identには、識別子の宣言箇所を指すObjフィールドがありますが、これはgo/typesのような正式な型検査器ではなく、go/parserが構文解析の副産物として簡易的に構築する「レガシー」な解決結果に過ぎません。構造体フィールドやimportされた識別子の解決に対応しないなど不完全であり、#52463で正式に非推奨とされ、go/typesの利用が推奨されています。
一方で、この解決処理はパース時間の20〜40%を占めるため、#46485で導入されたparser.SkipObjectResolutionモードを使ってスキップするのが性能上望ましいケースが多くあります。しかし、以下のような既存アプリケーションでは、パース後に何らかの理由で解決結果が必要になることがあります。

  • gopls: x/tools/go/analysisドライバとの後方互換性のため、アナライザに渡す構文木には解決済みのIdent.Objを用意する契約があります。goplsは通常性能のため解決処理をスキップしていますが、アナライザに渡す直前に遅延的に解決処理を適用する必要があり、そのためにgo/parser内部の解決アルゴリズムをparsego.File.Resolveとして重複実装しています。
  • ドキュメント生成ツール(go/doc利用者): #79614で報告されたように、go/doc.NewFromFilesが内部実装の変更(CL 675036)によりクロスファイルでの解決を行わなくなり、Ident.Objに依存していた既存ツールが壊れる事例が発生しました。
    これらのアプリケーションが、解決アルゴリズムそのものを自前で複製・再実装せざるを得ない状況が課題でした。

提案された解決策

go/parserパッケージに以下のシグネチャの関数を追加します。

package parser
// ResolveFile applies the parser's legacy ast.Ident-to-ast.Object resolution to the
// specified file's syntax tree. This is the same operation that is skipped on files
// parsed with the `SkipObjectResolution` mode flag. It is idempotent.
//
// Deprecated: [ast.Object] should not be used in new designs; use [go/types] instead.
// This function is provided to ease migration in applications that have disabled
// legacy object resolution by default but still need it in some circumstances.
func ResolveFile(*ast.File)

この関数は冪等(複数回呼んでも安全)であることが求められており、実装上はast.Fileが内部でsync.Onceのようなガードを持つことで、遅延・一度きりの解決処理を保証します。

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

SkipObjectResolutionモードで高速にパースしておき、後から必要になった一部のファイルだけに対して明示的に解決処理を適用する、という使い分けが公式APIとして可能になります。これにより、gopls等のツールが独自に解決アルゴリズムを複製・保守する必要がなくなり、go/parser本体との実装の乖離(バグの温床)を防げます。

コード例

// Before: goplsのようなツールは、性能のため SkipObjectResolution でパースしつつ、
// 一部のケースで解決が必要な場合、自前で解決アルゴリズムを再実装していた
// (例: x/tools/gopls/internal/cache/parsego.File.Resolve)
f, _ := parser.ParseFile(fset, filename, src, parser.SkipObjectResolution)
// ... 独自実装の resolve(f) を呼ぶ必要があった
// After: 標準ライブラリの ResolveFile を直接呼べる
f, _ := parser.ParseFile(fset, filename, src, parser.SkipObjectResolution)
if needsLegacyResolution {
    parser.ResolveFile(f) // 冪等なので複数回呼んでも安全
}

議論のハイライト

  • 提案者(@adonovan)は、ast.Object自体は#52463で既に非推奨化済みであり、新規設計では使うべきでないという前提に立ちつつ、既存の移行が容易でないアプリケーション(特にドキュメント専用ツール)への配慮として本関数を提案しています。
  • goplsがx/tools/go/analysisドライバとの後方互換性のために解決アルゴリズムを重複実装している事実が、公式APIを求める強い動機として挙げられています。
  • 関連issue #79614で報告された、go/doc.NewFromFilesの内部実装変更(CL 675036)によるcrossファイル解決の欠落という実害が、タイミング的にも本提案の後押しとなったと考えられます。
  • 本提案は最初からDeprecatedタグ付きで設計されており、「非推奨機能への移行を助けるための非推奨API追加」という一見矛盾する構成についての反対意見は見られず、レビュー会議でも一貫して意見の変化なく承認されています。
  • sync.Onceによるガードを用いた遅延・冪等な設計が明記されており、実装の詳細まで提案時点で詰められていたことが、スムーズな承認につながったと考えられます。

関連リンク