#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追加が続いています。この提案もその流れにあたります。