[インデックス 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言語のソースコード解析に関するブログ記事やチュートリアル。