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

x/tools/go/ast/inspector: add Cursor.Unparen{,Enclosing} methods

新規提案

要約

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

概要

golang.org/x/tools/go/ast/inspector パッケージの Cursor 型に、Unparen と UnparenEnclosing の2つのメソッドを追加する提案です。どちらも、AST(抽象構文木)を走査するときに括弧(*ast.ParenExpr)を読み飛ばすためのものです。

ステータス変更

(新規) → active
2026年10月29日、@aclements がproposal review groupを代表して、この提案をactive列に追加しました。今後は週次のproposal review meetingで検討されます。提案が出されたのは2026年4月28日です。提案者の @mateusz834 は、gopls/x/tools の内部パッケージ internal/astutil に同じ機能のヘルパーを追加しています(CL 771080)。この提案は、それを公開APIとして使えるようにするものです。

技術的背景

現状の問題点

Goのソースで (x) や ((f))(a) のように括弧で囲まれた式は、ASTでは *ast.ParenExpr ノードとして表されます。静的解析ツール(analyzer)を書くときに括弧の処理を忘れると、次のような場合に判定が漏れます。

  • 子方向: cur.Node() の型で switch すると、(f)() の (f) は *ast.Ident ではなく *ast.ParenExpr になる。
  • 親方向: cur.ParentEdge() で親との関係を調べると、x が (x) の中にある場合は親が ParenExpr になり、本当の文脈(代入の左辺なのか、関数呼び出しの引数なのかなど)が分からない。
    ast.Node については、Go 1.22 で ast.Unparen(#60061)が追加されています。一方、Cursor(#70859 で導入されたAST上の位置を表す型)には同等の手段がありませんでした。

提案された解決策

package inspector
// Unparen は、外側の括弧をすべて取り除いた式を指す Cursor を返す(ast.Unparen の Cursor 版)。
// cur.Node() の型で switch する前に呼ぶとよい。
func (cur Cursor) Unparen() Cursor
// UnparenEnclosing は、Cursor.Enclosing(自身から祖先へ向かう列)の要素のうち、
// 括弧の中にない最初のものを返す。cur.ParentEdge() で switch する前に呼ぶとよい。
func (cur Cursor) UnparenEnclosing() Cursor

Unparen はノードが ParenExpr である間、子へ降りていきます。UnparenEnclosing は ParentEdgeKind() == edge.ParenExpr_X である間、親へ上がっていきます。

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

  • 呼び出し式 (f)(x) の Fun に Unparen を適用し、関数名を正しく取り出す。
  • ある識別子が代入の左辺や &x のオペランドかどうかを、(x) の場合も含めて判定する(UnparenEnclosing().ParentEdge())。
  • コードを書き換えるanalyzerで、括弧付きの式を書き換えたときに起こる誤検出・検出漏れを防ぐ。

コード例

// Before: 括弧の処理を自分で書く必要がある
c := cur
for c.ParentEdgeKind() == edge.ParenExpr_X {
	c = c.Parent()
}
if ek, _ := c.ParentEdge(); ek == edge.AssignStmt_Lhs {
	// x が代入の左辺にある
}
// After: メソッドを呼ぶだけで済み、メソッドチェーンもできる
if ek, _ := cur.UnparenEnclosing().ParentEdge(); ek == edge.AssignStmt_Lhs {
	// (x) = 1 のような場合も検出できる
}
switch n := cur.Unparen().Node().(type) {
case *ast.Ident:
	_ = n
}

議論のハイライト

  • 名前: 内部ヘルパーは独立した関数だったため、UnparenCursor のように Cursor 接尾辞が付いていました。@adonovan は、メソッドにするなら接尾辞は不要だと指摘しました。提案者はこれを受けて、名前を Unparen / UnparenEnclosing に変更しました。
  • レシーバの種類: ポインタレシーバにして Cursor をその場で書き換える案も挙がりました。しかしそれでは cur.Parent().UnparenEnclosing() のようなメソッドチェーンが書けません。@adonovan も「Cursor は値型であり、どのメソッドもポインタレシーバにすべきでない」と同意し、値レシーバに決まりました。その場で書き換えたい場合は cur = cur.Unparen() と書けます。
  • 実装: @adonovan は、Unparen 内の cur.FirstChild() を cur.index++ にできると指摘しました。ParenExpr の最初の子はイベント列のすぐ次の位置にあるためです。
  • 関連する提案: ParentEdge{Kind,Index}(#77195)、Cursor.Valid(#76361)など、Cursor を使いやすくするAPI追加が続いています。この提案もその流れにあたります。

関連リンク