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

Go Proposal Weekly Digest

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

#77195likely_accept

x/tools/go/astutil/inspector: add Cursor.IsChildOf

ステータス変更: active likely_accept

要約

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

概要

x/tools/go/ast/inspectorパッケージのCursor型に、ParentEdge()メソッドが返す2つの値(edge.KindとIndex)を個別に取得できる便利メソッドParentEdgeKind()ParentEdgeIndex()を追加する提案です。これにより、親ノードとの関係性を判定する際のコードが簡潔になります。

ステータス変更

activelikely_accept
Proposal Review Meetingでの議論により、当初提案されていたIsChildOfメソッドから設計が変更され、より柔軟な2つの独立したアクセサメソッドを提供する形で承認見込みとなりました。この変更により、単純な等値比較だけでなく、より複雑な条件判定にも対応できるようになりました。

技術的背景

現状の問題点

inspector.Cursor.ParentEdge()メソッドは、カーソルの親ノードに対する「エッジの種類(edge.Kind)」と「リスト内のインデックス」の2つの値をペアで返します。これは両方を同時に計算する方が効率的なためですが、実際にはエッジの種類だけが必要な場合が大半です。
現在は以下のように2つ目の戻り値を_で破棄する必要があり、特に長い条件式の中では冗長になります:

if ek, _ := cur.ParentEdge(); ek == edge.CallExpr_Args { ... }

そのため、x/tools内部ではastutil.IsChildOfというヘルパー関数が使われており、現在23箇所で利用されています:

if astutil.IsChildOf(cur, edge.SelectorExpr_Sel) { ... }

しかし、この関数はinternalパッケージにあるため公開APIとして使えず、またメソッドではないため記述が直感的ではありません。

提案された解決策

当初はIsChildOfメソッドの追加が提案されましたが、レビュー過程での議論を経て、より汎用的な2つのアクセサメソッドを提供する形に変更されました:

package inspector // golang.org/x/tools/go/ast/inspector
type Cursor ...
// ParentEdge()の第1要素(edge.Kind)を返す
func (Cursor) ParentEdgeKind() edge.Kind
// ParentEdge()の第2要素(index)を返す
func (Cursor) ParentEdgeIndex() int

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

AST(抽象構文木)を走査する際、現在のノードが親ノードのどのフィールドに含まれているかを簡潔に判定できるようになります。これはGo言語の静的解析ツールやリファクタリングツールの開発において非常に重要な機能です。

コード例

// Before: 従来の書き方(不要な値を破棄する必要がある)
if ek, _ := cur.ParentEdge(); ek == edge.CallExpr_Args {
    // このノードは関数呼び出しの引数
}
// Before: 内部ヘルパー関数(publicには使えない)
if astutil.IsChildOf(cur, edge.SelectorExpr_Sel) {
    // このノードはセレクタの選択部分(x.Selのsel部分)
}
// After: 新APIを使った書き方(簡潔で直感的)
if cur.ParentEdgeKind() == edge.CallExpr_Args {
    // このノードは関数呼び出しの引数
}
// After: インデックスも必要な場合
if cur.ParentEdgeKind() == edge.CallExpr_Args && cur.ParentEdgeIndex() == 0 {
    // このノードは関数呼び出しの第1引数
}
// After: より複雑な条件(IsChildOfでは不可能)
if k := cur.ParentEdgeKind(); k == edge.CallExpr_Args || k == edge.CompositeLit_Elts {
    // 複数のエッジタイプをまとめて判定
}

実用例: 関数呼び出しの引数として使われているidentifierだけを検出したい場合、ParentEdgeKind() == edge.CallExpr_Argsで簡潔に判定できます。従来はParentEdge()を呼んで戻り値の一方を破棄する必要がありました。

議論のハイライト

  • 命名の議論: 当初IsChildOfが提案されたが、@DanielMorsingから「"Child of"はノードタイプを示唆し、エッジに対しては違和感がある」との指摘があり、HasParentEdgeKindなどの代替案が検討されました
  • 設計の改善: @aclementsから「Hasは単なる==チェックなので、ParentEdgeKind()を提供して呼び出し側で柔軟に比較させる方が良い」との提案があり、2つの独立したアクセサメソッドを提供する最終形に至りました
  • 柔軟性の向上: メソッド形式に変更することで、単純な等値比較だけでなく、複数の値との比較(||)やスイッチ文での使用など、より多様な使い方が可能になりました
  • 実装CL: 既に実装CL(go.dev/cl/740280)が作成されており、Final Comment Period(最終コメント期間)に入っています
  • 広範な利用実績: x/tools内部で既に23箇所で使われている実績があり、実用性が実証されています

関連リンク