[インデックス 16381] ファイルの概要
このコミットは、Go言語の抽象構文木(AST)を扱う go/ast パッケージ内の FuncType ノードに関する修正とドキュメントの改善を目的としています。具体的には、FuncType.Pos() メソッドの実装が修正され、FuncType.Params および ChanType.Arrow フィールドのドキュメントがより正確になるように更新されています。
go/ast パッケージは、Goプログラムのソースコードを解析して抽象構文木を構築するための型と関数を提供します。ASTは、コンパイラ、リンター、コードフォーマッター、静的解析ツールなど、Goコードをプログラム的に操作する多くのツールにとって不可欠な中間表現です。
このコミットで変更されたファイルは以下の通りです。
src/pkg/go/ast/ast.go:go/astパッケージの主要な型定義が含まれています。FuncType構造体とそのPos()メソッド、ChanType構造体などが定義されています。src/pkg/go/ast/filter.go: ASTのフィルタリングや操作に関連するユーティリティ関数が含まれています。このファイルではtoken.NoPosの利用箇所が修正されています。
コミット
go/ast: FuncType.Pos() の実装と FuncType.Params のドキュメントを修正
adonovan氏の指摘による。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/75b62e367bfe0b4935fb08f5941f3a9a99932dae
元コミット内容
commit 75b62e367bfe0b4935fb08f5941f3a9a99932dae
Author: Robert Griesemer <gri@golang.org>
Date: Wed May 22 13:36:43 2013 -0700
go/ast: fix FuncType.Pos() impl. and FuncType.Params documentation
As pointed out by adonovan.
R=golang-dev, adonovan
CC=golang-dev
https://golang.org/cl/9662045
変更の背景
このコミットの背景には、Go言語のAST(抽象構文木)を扱う go/ast パッケージにおける FuncType ノードの Pos() メソッドの不正確な振る舞いと、関連するドキュメントの誤りがありました。
go/ast パッケージでは、ソースコード内の各要素(識別子、キーワード、式など)が token.Pos 型で表される位置情報を持っています。これは、エラー報告、コードのハイライト、リファクタリングツールなど、ソースコードの正確な位置を特定する必要がある場合に非常に重要です。
FuncType は関数型を表すASTノードであり、その Pos() メソッドは、その関数型がソースコード内で始まる位置を返すことが期待されます。しかし、元の実装では、FuncType.Func フィールド(func キーワードの位置)を無条件に返していました。
問題は、インターフェースのメソッド宣言のように、func キーワードが存在しない関数型も存在するという点です。このような場合、FuncType.Func は token.NoPos(無効な位置)となるため、FuncType.Pos() が常に token.NoPos を返してしまうというバグがありました。これは、インターフェースメソッドのASTを正確に処理しようとするツールにとって問題となります。
また、FuncType.Params フィールドのドキュメントが「(incoming) parameters; or nil」となっていましたが、実際にはパラメータがない場合でも FieldList は nil ではなく、空の FieldList が設定されるべきでした。同様に、ChanType.Arrow のドキュメントも token.NoPos の意味合いを明確にする必要がありました。
これらの問題は、Go言語のツール開発者であるadonovan氏によって指摘され、そのフィードバックに基づいてこの修正がコミットされました。目的は、go/ast パッケージの正確性と堅牢性を向上させ、ASTを扱うツールがより信頼性の高い位置情報を取得できるようにすることです。
前提知識の解説
抽象構文木 (Abstract Syntax Tree, AST)
ASTは、プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラやインタープリタがソースコードを解析する際の中間表現として広く利用されます。ASTは、ソースコードの具体的な構文(括弧やセミコロンなど)を抽象化し、プログラムの論理的な構造に焦点を当てます。
Go言語では、go/ast パッケージがGoプログラムのASTを構築するための型と関数を提供します。例えば、関数宣言は ast.FuncDecl、変数宣言は ast.GenDecl、式は ast.Expr などのノードで表現されます。
go/ast パッケージ
go/ast パッケージは、Go言語のソースコードを解析してASTを生成し、そのASTを操作するための基本的な構成要素を提供します。主要な型には以下のようなものがあります。
Node: AST内のすべてのノードが実装するインターフェース。Expr: 式を表すノード。Stmt: 文を表すノード。Decl: 宣言を表すノード。File: 単一のGoソースファイル全体のASTを表すノード。Package: 複数のファイルからなるパッケージのASTを表すノード。
token.Pos と token.NoPos
go/token パッケージは、ソースコード内の位置情報を表す Pos 型と、無効な位置を表す NoPos 定義を提供します。
token.Pos: ソースコード内の特定の文字位置を整数で表します。これは通常、ファイル内のオフセットとして扱われます。token.NoPos:token.Posのゼロ値であり、有効な位置情報がないことを示します。例えば、ASTノードがソースコードの特定のキーワードに対応しない場合や、合成されたノードで位置情報が不要な場合に使用されます。IsValid()メソッドでtoken.Posが有効かどうかをチェックできます。
FuncType 構造体
go/ast パッケージの FuncType 構造体は、関数型(例: func(int, string) (bool, error))をASTで表現するためのノードです。
type FuncType struct {
Func token.Pos // position of "func" keyword
Params *FieldList // (incoming) parameters; or nil
Results *FieldList // (outgoing) results; or nil
}
Func:funcキーワードの開始位置。インターフェースメソッド宣言のようにfuncキーワードがない場合はtoken.NoPosになります。Params: 関数の引数を表すFieldListへのポインタ。Results: 関数の戻り値を表すFieldListへのポインタ。
FieldList 構造体
FieldList は、関数パラメータや構造体フィールドのリストを表すために使用されます。
type FieldList struct {
Opening token.Pos // position of opening parenthesis '(' or '{'
List []*Field // field list; or nil
Closing token.Pos // position of closing parenthesis ')' or '}'
}
Opening: 開き括弧((または{)の位置。List:Fieldのスライス。各Fieldは単一のパラメータまたはフィールドを表します。Closing: 閉じ括弧()または})の位置。
FieldList.Pos() メソッドは、Opening フィールドが有効であればその位置を返し、そうでなければ List 内の最初の Field の位置を返します。
インターフェースメソッド宣言
Go言語のインターフェースは、メソッドのシグネチャの集合を定義します。インターフェース内で宣言されるメソッドは、func キーワードを持ちません。例えば:
type MyInterface interface {
MyMethod(int) string
}
この MyMethod の型は func(int) string ですが、ソースコード上には func キーワードが存在しません。ASTでは、このようなメソッドも FuncType ノードとして表現されますが、その Func フィールドは token.NoPos となります。
技術的詳細
このコミットは、主に以下の2つの技術的な側面に焦点を当てています。
-
FuncType.Pos()メソッドの修正: 元のFuncType.Pos()メソッドは、単にx.Funcを返していました。これは、通常の関数宣言(例:func foo() {})では問題ありませんが、インターフェースのメソッド宣言(例:type I interface { M() })のようにfuncキーワードが存在しない場合、x.Funcはtoken.NoPosとなります。その結果、FuncType.Pos()もtoken.NoPosを返してしまい、インターフェースメソッドのASTノードの正確な開始位置を取得できないという問題がありました。修正後の実装では、まず
x.Func.IsValid()をチェックします。x.Funcが有効な位置(つまりfuncキーワードが存在する)であれば、その位置を返します。x.Funcが無効な位置(token.NoPos)であれば、それはインターフェースメソッド宣言であると判断し、代わりにx.Params.Pos()を返します。x.Paramsは関数のパラメータリストを表すFieldListであり、そのPos()メソッドはパラメータリストの開始位置(通常は開き括弧(の位置)を返します。これにより、funcキーワードがない場合でも、関数型の開始位置を正確に特定できるようになります。
-
ドキュメントの更新:
FuncType.Funcフィールドのコメントが「position of "func" keyword」から「position of "func" keyword (token.NoPos if there is no "func")」に変更されました。これにより、funcキーワードが存在しない場合のtoken.NoPosの意味合いが明確になりました。FuncType.Paramsフィールドのコメントが「(incoming) parameters; or nil」から「(incoming) parameters; non-nil」に変更されました。これは、パラメータがない場合でもFieldListがnilではなく、空のFieldListが設定されるというASTの内部的な不変条件を反映しています。これにより、ASTを扱うツールがParamsがnilである可能性を考慮する必要がなくなり、コードの堅牢性が向上します。ChanType.Arrowフィールドのコメントも同様に「position of "<-" (noPos if there is no "<-")」から「position of "<-" (token.NoPos if there is no "<-")」に変更され、token.NoPosの使用が明確化されました。
-
noPos変数の削除とtoken.NoPosの直接使用: 以前はvar noPos token.Posというグローバル変数が定義され、token.NoPosの代わりにnoPosが使用されていました。このコミットでは、このnoPos変数が削除され、代わりにtoken.NoPos定数が直接使用されるようになりました。これは、コードの明確性を高め、冗長性を排除するためのクリーンアップです。token.NoPosは定数であるため、グローバル変数として保持する必要はありません。
これらの変更により、go/ast パッケージはより正確なAST表現を提供し、Go言語の静的解析ツールやコード生成ツールがより信頼性の高い情報を利用できるようになります。特に、インターフェースメソッドの解析における正確性が向上しました。
コアとなるコードの変更箇所
diff --git a/src/pkg/go/ast/ast.go b/src/pkg/go/ast/ast.go
index bf533d1d24..e8599184a6 100644
--- a/src/pkg/go/ast/ast.go
+++ b/src/pkg/go/ast/ast.go
@@ -385,8 +385,8 @@ type (
// A FuncType node represents a function type.
FuncType struct {
- Func token.Pos // position of "func" keyword
- Params *FieldList // (incoming) parameters; or nil
+ Func token.Pos // position of "func" keyword (token.NoPos if there is no "func")
+ Params *FieldList // (incoming) parameters; non-nil
Results *FieldList // (outgoing) results; or nil
}
@@ -407,7 +407,7 @@ type (
// A ChanType node represents a channel type.
ChanType struct {
Begin token.Pos // position of "chan" keyword or "<-" (whichever comes first)
- Arrow token.Pos // position of "<-" (noPos if there is no "<-")
+ Arrow token.Pos // position of "<-" (token.NoPos if there is no "<-")
Dir ChanDir // channel direction
Value Expr // value type
}
@@ -438,10 +438,15 @@ func (x *BinaryExpr) Pos() token.Pos { return x.X.Pos() }\n func (x *KeyValueExpr) Pos() token.Pos { return x.Key.Pos() }\n func (x *ArrayType) Pos() token.Pos { return x.Lbrack }\n func (x *StructType) Pos() token.Pos { return x.Struct }\n-func (x *FuncType) Pos() token.Pos { return x.Func }\n-func (x *InterfaceType) Pos() token.Pos { return x.Interface }\n-func (x *MapType) Pos() token.Pos { return x.Map }\n-func (x *ChanType) Pos() token.Pos { return x.Begin }\n+func (x *FuncType) Pos() token.Pos {\n+ if x.Func.IsValid() {\n+ return x.Func\n+ }\n+ return x.Params.Pos() // interface method declarations have no "func" keyword\n+}\n+func (x *InterfaceType) Pos() token.Pos { return x.Interface }\n+func (x *MapType) Pos() token.Pos { return x.Map }\n+func (x *ChanType) Pos() token.Pos { return x.Begin }\n
func (x *BadExpr) End() token.Pos { return x.To }\n func (x *Ident) End() token.Pos { return token.Pos(int(x.NamePos) + len(x.Name)) }\n @@ -511,12 +516,10 @@ func (*ChanType) exprNode() {}\n // ----------------------------------------------------------------------------\n // Convenience functions for Idents\n \n-var noPos token.Pos\n-\n // NewIdent creates a new Ident without position.\n // Useful for ASTs generated by code other than the Go parser.\n //\n-func NewIdent(name string) *Ident { return &Ident{noPos, name, nil} }\n+func NewIdent(name string) *Ident { return &Ident{token.NoPos, name, nil} }\n \n // IsExported returns whether name is an exported Go symbol\n // (i.e., whether it begins with an uppercase letter).\ndiff --git a/src/pkg/go/ast/filter.go b/src/pkg/go/ast/filter.go\nindex 71c9ed7766..fc3eeb4a1d 100644\n--- a/src/pkg/go/ast/filter.go\n+++ b/src/pkg/go/ast/filter.go\n@@ -308,7 +308,7 @@ func nameOf(f *FuncDecl) string {\n // separator is an empty //-style comment that is interspersed between\n // different comment groups when they are concatenated into a single group\n //\n-var separator = &Comment{noPos, \"//\"}\n+var separator = &Comment{token.NoPos, \"//\"}\n \n // MergePackageFiles creates a file AST by merging the ASTs of the\n // files belonging to a package. The mode flags control merging behavior.\n```
## コアとなるコードの解説
### `src/pkg/go/ast/ast.go` の変更点
1. **`FuncType` 構造体のコメント修正**:
```diff
- Func token.Pos // position of "func" keyword
- Params *FieldList // (incoming) parameters; or nil
+ Func token.Pos // position of "func" keyword (token.NoPos if there is no "func")
+ Params *FieldList // (incoming) parameters; non-nil
```
* `Func` フィールドのコメントに「(token.NoPos if there is no "func")」が追記されました。これにより、`func` キーワードが存在しない(例: インターフェースメソッド)場合に `Func` が `token.NoPos` となることが明示され、ドキュメントの正確性が向上しました。
* `Params` フィールドのコメントが「(incoming) parameters; or nil」から「(incoming) parameters; non-nil」に変更されました。これは、パラメータがない場合でも `Params` フィールドが `nil` になることはなく、空の `FieldList` を指すポインタが設定されるというASTの不変条件を明確にしています。これにより、ASTを処理するコードが `nil` チェックを不要に実行するのを防ぎ、より堅牢になります。
2. **`ChanType` 構造体のコメント修正**:
```diff
- Arrow token.Pos // position of "<-" (noPos if there is no "<-")
+ Arrow token.Pos // position of "<-" (token.NoPos if there is no "<-")
```
* `Arrow` フィールドのコメントが「(noPos if there is no "<-")」から「(token.NoPos if there is no "<-")」に変更されました。これは、`noPos` 変数が削除され、`token.NoPos` 定数が直接使用されるようになったことに伴う修正であり、一貫性を保つためのものです。
3. **`FuncType.Pos()` メソッドの実装修正**:
```diff
-func (x *FuncType) Pos() token.Pos { return x.Func }
+func (x *FuncType) Pos() token.Pos {
+ if x.Func.IsValid() {
+ return x.Func
+ }
+ return x.Params.Pos() // interface method declarations have no "func" keyword
+}
```
* この変更がこのコミットの最も重要な部分です。
* 元の実装は単に `x.Func` を返していました。
* 新しい実装では、まず `x.Func.IsValid()` をチェックします。
* `x.Func` が有効な位置(`func` キーワードが存在する)であれば、その位置を返します。
* `x.Func` が無効な位置(`token.NoPos`)であれば、それはインターフェースメソッド宣言のように `func` キーワードが存在しないケースであると判断し、代わりに `x.Params.Pos()` を返します。`x.Params.Pos()` はパラメータリストの開始位置(通常は開き括弧 `(` の位置)を返すため、`func` キーワードがない場合でも関数型の正確な開始位置を提供できます。コメント「interface method declarations have no "func" keyword」がこのロジックの意図を明確にしています。
4. **`noPos` 変数の削除と `NewIdent` 関数の修正**:
```diff
-var noPos token.Pos
-
// NewIdent creates a new Ident without position.
// Useful for ASTs generated by code other than the Go parser.
//
-func NewIdent(name string) *Ident { return &Ident{noPos, name, nil} }
+func NewIdent(name string) *Ident { return &Ident{token.NoPos, name, nil} }
```
* グローバル変数 `noPos` が削除されました。
* `NewIdent` 関数内で `noPos` を使用していた箇所が `token.NoPos` に直接置き換えられました。これにより、コードの冗長性がなくなり、`token.NoPos` の意図がより明確になります。
### `src/pkg/go/ast/filter.go` の変更点
1. **`separator` 変数の修正**:
```diff
-var separator = &Comment{noPos, "//"}
+var separator = &Comment{token.NoPos, "//"}
```
* `src/pkg/go/ast/ast.go` と同様に、`noPos` 変数の削除に伴い、`separator` 変数の初期化で `noPos` を使用していた箇所が `token.NoPos` に直接置き換えられました。
これらの変更は、`go/ast` パッケージの内部的な整合性と正確性を高め、特にASTノードの位置情報に関する信頼性を向上させるものです。これにより、Go言語のツールエコシステム全体が恩恵を受けます。
## 関連リンク
* Go言語の `go/ast` パッケージに関する公式ドキュメント: [https://pkg.go.dev/go/ast](https://pkg.go.dev/go/ast)
* Go言語の `go/token` パッケージに関する公式ドキュメント: [https://pkg.go.dev/go/token](https://pkg.go.dev/go/token)
## 参考にした情報源リンク
* GitHubコミットページ: [https://github.com/golang/go/commit/75b62e367bfe0b4935fb08f5941f3a9a99932dae](https://github.com/golang.com/go/commit/75b62e367bfe0b4935fb08f5941f3a9a99932dae)
* Go CL 9662045: [https://golang.org/cl/9662045](https://golang.org/cl/9662045) (これは古いGoのコードレビューシステムへのリンクであり、現在はGitHubのプルリクエストにリダイレクトされるか、アクセスできない場合があります。)
* Go言語のASTに関する一般的な情報源 (例: Go AST Explorerなど、ASTの構造を視覚的に理解するのに役立つツール)
* Go言語のインターフェースとメソッドに関する公式ドキュメント。
* `token.Pos` と `token.NoPos` の概念に関するGo言語の仕様または関連ドキュメント。
* Go言語のソースコード解析に関するブログ記事やチュートリアル。