[インデックス 14007] ファイルの概要
このコミットは、Go言語の抽象構文木(AST)を扱う go/ast
パッケージにおいて、チャネル型(chan
)の定義における <-
演算子の正確な位置情報を追跡するように変更を加えるものです。これにより、特定のチャネル型が持つ位置情報の不正確さを修正し、ASTの正確性を向上させます。この変更は後方互換性のあるAPI変更として扱われています。
コミット
commit 1659aef3990377081277c48304f7b63a3ce19092
Author: Robert Griesemer <gri@golang.org>
Date: Tue Oct 2 17:50:36 2012 -0700
go/ast: track position of <- for channel types
This is a backward-compatible API change.
Without the correct <- position information,
certain channel types have incorrect position
information.
R=iant, iant
CC=golang-dev
https://golang.org/cl/6585063
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1659aef3990377081277c48304f7b63a3ce19092
元コミット内容
このコミットの目的は、Go言語のコンパイラやツールが内部的に使用する抽象構文木(AST)において、チャネル型(chan
)の定義に含まれる <-
記号の位置情報を正確に追跡することです。コミットメッセージによると、これまでの実装では <-
の正確な位置情報が欠けていたため、特定のチャネル型(特に送受信の方向が指定されたチャネル型、例: <-chan int
や chan<- int
)のASTノードが不正確な位置情報を持つ可能性がありました。この修正はAPIの後方互換性を維持しつつ、この問題を解決します。
変更の背景
Go言語のコンパイラや開発ツールは、ソースコードを解析して抽象構文木(AST)を構築します。ASTは、プログラムの構造を木構造で表現したもので、コンパイラのセマンティック解析、コード生成、静的解析ツール、IDEの機能(コード補完、リファクタリングなど)の基盤となります。
チャネル型はGo言語の並行処理において非常に重要な要素であり、その構文は chan T
(双方向チャネル)、<-chan T
(受信専用チャネル)、chan<- T
(送信専用チャネル) のように定義されます。これらの型定義において、<-
記号はチャネルの方向を示す重要な役割を果たします。
コミットメッセージにある「Without the correct <- position information, certain channel types have incorrect position information.」という記述は、既存のAST表現では <-
記号の正確なソースコード上の位置が記録されていなかったことを示唆しています。これにより、例えばエラーメッセージの表示、デバッグ情報の生成、あるいはコードフォーマッタ(go fmt
など)がチャネル型を正しく整形する際に問題が生じる可能性がありました。正確な位置情報は、これらのツールがユーザーに正確なフィードバックを提供し、コードを適切に処理するために不可欠です。
このコミットは、ASTノードに <-
記号の位置情報を明示的に追加することで、この不正確さを解消し、Go言語のツールチェイン全体の堅牢性と正確性を向上させることを目的としています。
前提知識の解説
1. Go言語のチャネル型
Go言語のチャネルは、ゴルーチン間で値を送受信するための通信メカニズムです。チャネルには以下の3つの主要な型があります。
chan T
: 双方向チャネル。T
型の値を送受信できます。<-chan T
: 受信専用チャネル。T
型の値を受信することのみが可能です。<-
がchan
の左側にあります。chan<- T
: 送信専用チャネル。T
型の値を送信することのみが可能です。<-
がchan
の右側にあります。
これらのチャネル型は、関数の引数や戻り値として使用することで、ゴルーチン間の通信の方向性を強制し、より安全な並行処理を記述するのに役立ちます。
2. 抽象構文木 (Abstract Syntax Tree: AST)
ASTは、プログラミング言語のソースコードの抽象的な構文構造を木構造で表現したものです。各ノードはソースコード内の構成要素(変数、関数、式、文など)を表し、エッジはそれらの間の関係を表します。
Go言語では、go/ast
パッケージがGoプログラムのASTを定義しています。コンパイラやリンター、フォーマッタなどのツールは、まずソースコードを解析してASTを構築し、そのASTを操作することで様々な処理を行います。
3. go/ast
パッケージ
go/ast
パッケージは、Go言語のソースコードのASTノードを定義しています。例えば、ast.FuncDecl
は関数宣言を表し、ast.Expr
は式を表すインターフェースです。このコミットで変更される ast.ChanType
は、チャネル型を表す構造体です。
4. token.Pos
go/token
パッケージは、Goソースコード内のトークン(キーワード、識別子、演算子など)とそれらの位置情報(ファイル名、行番号、列番号、オフセット)を扱います。token.Pos
型は、ソースコード内の特定の位置を表す整数値です。これは、エラーメッセージの正確な位置表示や、コードの整形において非常に重要です。
5. go/parser
パッケージ
go/parser
パッケージは、Goソースコードを解析して go/ast
パッケージで定義されたASTを構築する役割を担います。このパッケージは、ソースコードをトークンに分割し(字句解析)、そのトークン列から構文木を構築します(構文解析)。
6. go/printer
パッケージ
go/printer
パッケージは、ASTをGoソースコードとして整形して出力する役割を担います。go fmt
コマンドはこのパッケージを利用してGoコードを標準的なスタイルに整形します。
技術的詳細
このコミットの核心は、ast.ChanType
構造体に Arrow token.Pos
フィールドを追加し、パーサーとプリンターがこの新しい位置情報を適切に処理するように変更することです。
src/pkg/go/ast/ast.go
の変更
ast.ChanType
構造体に Arrow token.Pos
フィールドが追加されました。
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 "<-")
Dir ChanDir // channel direction
Value Expr // value type
}
Begin token.Pos
: 以前から存在し、「chan」キーワードまたは「<-」のいずれか早い方の位置を保持していました。Arrow token.Pos
: 新しく追加されたフィールドで、<-
演算子の正確な位置を保持します。<-
が存在しない場合はtoken.NoPos
(ゼロ値) になります。
この変更により、ASTノード自体が <-
の位置を直接保持できるようになり、より詳細なソースコード情報がASTに埋め込まれることになります。
src/pkg/go/parser/parser.go
の変更
go/parser
パッケージは、ソースコードを解析してASTを構築する際に、この新しい Arrow
フィールドに適切な値を設定するように変更されました。
-
parseChanType()
関数:chan T
のような双方向チャネルの場合、Arrow
はtoken.NoPos
に設定されます。<-chan T
のような受信専用チャネルの場合、p.expect(token.ARROW)
の結果として返されるtoken.Pos
がarrow
変数に格納され、ast.ChanType
のArrow
フィールドに設定されます。chan<- T
のような送信専用チャネルの場合、p.tok == token.ARROW
のチェックでp.pos
がarrow
変数に格納され、ast.ChanType
のArrow
フィールドに設定されます。- これにより、チャネルの方向を示す
<-
が存在する場合、その正確な位置がASTに記録されるようになります。
-
parseUnaryExpr()
関数:- 単項演算子としての
<-
(チャネルからの受信操作) を解析する部分も変更されました。 - 以前は
pos := p.pos
で<-
の位置をpos
に格納していましたが、新しいコードではarrow := p.pos
とし、arrow
変数に格納しています。 - 特に、
<-chan<-chan T
のようなネストされたチャネル型を解析するロジックが修正されました。以前はtyp.Begin
をpos
で上書きしていましたが、新しいコードではarrow, typ.Begin, typ.Arrow = typ.Arrow, arrow, arrow
のように、Arrow
フィールドを適切に伝播させるように変更されています。これにより、ネストされたチャネル型でも各<-
の位置が正しく追跡されるようになります。 - エラーメッセージの表示も
p.errorExpected(typ.Arrow, "'chan'")
のように、Arrow
フィールドの位置情報を使用するように変更され、より正確なエラー報告が可能になります。
- 単項演算子としての
src/pkg/go/parser/short_test.go
の変更
テストケースが更新され、新しいエラーメッセージの期待値が反映されています。
_ = (<-chan<-chan<-chan<-chan<-chan /* ERROR "expected 'chan'" */ <-int)(nil)
が_ = (<-chan<-chan<-chan<-chan<-chan<- /* ERROR "expected channel type" */ int)(nil)
に変更されました。これは、パーサーが<-
の後に期待するものが「chan」ではなく「channel type」になったことを示しており、より正確なエラー報告が可能になったことを意味します。
src/pkg/go/printer/nodes.go
の変更
go/printer
パッケージは、ASTをソースコードに変換する際に、新しい Arrow
フィールドを利用してチャネル型を正しく整形するように変更されました。
printer.expr1
メソッド内のast.ChanType
の処理部分:- 受信専用チャネル (
ast.RECV
) の場合:p.print(token.ARROW, token.CHAN)
は変更されていませんが、コメントに// x.Arrow and x.Pos() are the same
と追記され、Arrow
フィールドがBegin
フィールドと同じ位置を指すことを示唆しています。 - 送信専用チャネル (
ast.SEND
) の場合: 以前はp.print(token.CHAN, token.ARROW)
でしたが、新しいコードではp.print(token.CHAN, x.Arrow, token.ARROW)
となっています。これは、x.Arrow
の位置情報を使用して<-
記号を正確な位置に出力することを意味します。これにより、go fmt
などのツールがチャネル型を整形する際に、<-
の位置をより正確に再現できるようになります。
- 受信専用チャネル (
これらの変更により、Go言語のASTはチャネル型の <-
演算子の位置をより詳細に保持できるようになり、パーサー、プリンター、およびその他のツールがこの情報を利用してより正確な処理を行えるようになります。
コアとなるコードの変更箇所
diff --git a/src/pkg/go/ast/ast.go b/src/pkg/go/ast/ast.go
index d2e75dc1c0..e1582c3006 100644
--- a/src/pkg/go/ast/ast.go
+++ b/src/pkg/go/ast/ast.go
@@ -407,6 +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 "<-")
Dir ChanDir // channel direction
Value Expr // value type
}
diff --git a/src/pkg/go/parser/parser.go b/src/pkg/go/parser/parser.go
index 9c1459f40c..139de9fb22 100644
--- a/src/pkg/go/parser/parser.go
+++ b/src/pkg/go/parser/parser.go
@@ -924,20 +924,22 @@ func (p *parser) parseChanType() *ast.ChanType {
pos := p.pos
dir := ast.SEND | ast.RECV
+ var arrow token.Pos
if p.tok == token.CHAN {
p.next()
if p.tok == token.ARROW {
+ arrow = p.pos
p.next()
dir = ast.SEND
}
} else {
-\t\tp.expect(token.ARROW)
+\t\tarrow = p.expect(token.ARROW)
\t\tp.expect(token.CHAN)
\t\tdir = ast.RECV
\t}\n value := p.parseType()\n
-\treturn &ast.ChanType{Begin: pos, Dir: dir, Value: value}\n+\treturn &ast.ChanType{Begin: pos, Arrow: arrow, Dir: dir, Value: value}\n }\n \n // If the result is an identifier, it is not resolved.\n@@ -1397,7 +1399,7 @@ func (p *parser) parseUnaryExpr(lhs bool) ast.Expr {\n
case token.ARROW:\n \t// channel type or receive expression\n-\t\tpos := p.pos\n+\t\tarrow := p.pos\n \t\tp.next()\n \n \t\t// If the next token is token.CHAN we still don\'t know if it\n@@ -1421,29 +1423,25 @@ func (p *parser) parseUnaryExpr(lhs bool) ast.Expr {\n \t\t\t// (<-type)\n \n \t\t\t// re-associate position info and <-\n-\t\t\tarrow := true\n-\t\t\tfor ok && arrow {\n-\t\t\t\tbegin := typ.Begin\n+\t\t\tdir := ast.SEND\n+\t\t\tfor ok && dir == ast.SEND {\n \t\t\t\tif typ.Dir == ast.RECV {\n \t\t\t\t\t// error: (<-type) is (<-(<-chan T))\n-\t\t\t\t\tp.errorExpected(begin, \"\'chan\'\")\n+\t\t\t\t\tp.errorExpected(typ.Arrow, \"\'chan\'\")\n \t\t\t\t}\n-\t\t\t\tarrow = typ.Dir == ast.SEND\n-\t\t\t\ttyp.Begin = pos\n-\t\t\t\ttyp.Dir = ast.RECV\n+\t\t\t\tarrow, typ.Begin, typ.Arrow = typ.Arrow, arrow, arrow\n+\t\t\t\tdir, typ.Dir = typ.Dir, ast.RECV\n \t\t\t\ttyp, ok = typ.Value.(*ast.ChanType)\n-\t\t\t\t// TODO(gri) ast.ChanType should store exact <- position\n-\t\t\t\tpos = begin // estimate (we don\'t have the exact position of <- for send channels)\n \t\t\t}\n-\t\t\tif arrow {\n-\t\t\t\tp.errorExpected(pos, \"\'chan\'\")\n+\t\t\tif dir == ast.SEND {\n+\t\t\t\tp.errorExpected(arrow, \"channel type\")\n \t\t\t}\n \n \t\t\treturn x\n \t\t}\n \n \t\t// <-(expr)\n-\t\treturn &ast.UnaryExpr{OpPos: pos, Op: token.ARROW, X: p.checkExpr(x)}\n+\t\treturn &ast.UnaryExpr{OpPos: arrow, Op: token.ARROW, X: p.checkExpr(x)}\n \n \tcase token.MUL:\n \t\t// pointer type or unary \"*\" expression\ndiff --git a/src/pkg/go/parser/short_test.go b/src/pkg/go/parser/short_test.go
index d5856e4f95..daba853088 100644
--- a/src/pkg/go/parser/short_test.go
+++ b/src/pkg/go/parser/short_test.go
@@ -69,7 +69,7 @@ var invalids = []string{
`package p; var a = <- /* ERROR "expected expression" */ chan int;`,
`package p; func f() { select { case _ <- chan /* ERROR "expected expression" */ int: } };`,
`package p; func f() { _ = (<-<- /* ERROR "expected 'chan'" */ chan int)(nil) };`,
-\t`package p; func f() { _ = (<-chan<-chan<-chan<-chan<-chan /* ERROR "expected 'chan'" */ <-int)(nil) };`,
+\t`package p; func f() { _ = (<-chan<-chan<-chan<-chan<-chan<- /* ERROR "expected channel type" */ int)(nil) };`,
}\n \n func TestInvalid(t *testing.T) {\ndiff --git a/src/pkg/go/printer/nodes.go b/src/pkg/go/printer/nodes.go
index 04f2adbd87..01a7473b83 100644
--- a/src/pkg/go/printer/nodes.go
+++ b/src/pkg/go/printer/nodes.go
@@ -853,9 +853,9 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int) {\n \t\tcase ast.SEND | ast.RECV:\n \t\t\tp.print(token.CHAN)\n \t\tcase ast.RECV:\n-\t\t\tp.print(token.ARROW, token.CHAN)\n+\t\t\tp.print(token.ARROW, token.CHAN) // x.Arrow and x.Pos() are the same\n \t\tcase ast.SEND:\n-\t\t\tp.print(token.CHAN, token.ARROW)\n+\t\t\tp.print(token.CHAN, x.Arrow, token.ARROW)\n \t\t}\n \t\tp.print(blank)\n \t\tp.expr(x.Value)\n```
## コアとなるコードの解説
### `src/pkg/go/ast/ast.go`
* **`ChanType` 構造体への `Arrow token.Pos` フィールドの追加**:
* この変更は、チャネル型定義における `<-` 記号のソースコード上の正確な位置をASTノード自体が保持できるようにするための基盤となります。
* `token.Pos` はソースコード内の位置(ファイル、行、列、オフセット)を抽象化した型であり、この情報を保持することで、コンパイラやツールがより正確なエラー報告やコード整形を行うことが可能になります。
* `noPos` は `token.Pos` のゼロ値であり、`<-` が存在しない双方向チャネル (`chan T`) の場合にはこの値が設定されます。
### `src/pkg/go/parser/parser.go`
* **`parseChanType()` 関数における `Arrow` フィールドの設定**:
* この関数はチャネル型を解析し、`ast.ChanType` ノードを構築します。
* `if p.tok == token.CHAN` ブロック内で、`chan<- T` のような送信専用チャネルの場合に `arrow = p.pos` で `<-` の位置を記録しています。
* `else` ブロック内で、`<-chan T` のような受信専用チャネルの場合に `arrow = p.expect(token.ARROW)` で `<-` の位置を記録しています。`p.expect` は指定されたトークンを期待し、その位置を返します。
* 最終的に `return &ast.ChanType{Begin: pos, Arrow: arrow, Dir: dir, Value: value}` で、新しく追加された `Arrow` フィールドに解析された `<-` の位置情報を設定しています。
* **`parseUnaryExpr()` 関数における `Arrow` フィールドの伝播とエラー報告の改善**:
* この関数は単項演算子(`<-` を含む)を解析します。
* `case token.ARROW:` のブロック内で、単項の `<-` 演算子の位置を `arrow := p.pos` で取得しています。
* 特に重要なのは、ネストされたチャネル型(例: `<-chan <-chan int`)の解析ロジックの変更です。以前は `typ.Begin` を上書きしていましたが、新しいコードでは `arrow, typ.Begin, typ.Arrow = typ.Arrow, arrow, arrow` という行で、`Arrow` フィールドの値を適切に伝播させています。これにより、内側のチャネル型の `Arrow` 位置が外側のチャネル型の `Begin` 位置として、また外側のチャネル型の `Arrow` 位置として正しく設定されるようになります。これは、ASTがネストされた構造を持つ場合に、各 `<-` の位置を正確に保持するために不可欠です。
* エラーメッセージの表示も `p.errorExpected(typ.Arrow, "'chan'")` や `p.errorExpected(arrow, "channel type")` のように、`Arrow` フィールドが持つ正確な位置情報を使用するように変更され、ユーザーにとってより分かりやすいエラー報告が可能になります。
### `src/pkg/go/parser/short_test.go`
* **テストケースの更新**:
* 不正な構文のテストケースが更新され、パーサーが報告するエラーメッセージの期待値が変更されています。
* `expected 'chan'` から `expected channel type` への変更は、パーサーが `<-` の後に期待するものがより一般的な「チャネル型」になったことを示しており、パーサーのロジックがより洗練されたことを意味します。
### `src/pkg/go/printer/nodes.go`
* **`printer.expr1` 関数における `Arrow` フィールドの利用**:
* この関数はASTノードをGoソースコードとして出力します。
* 送信専用チャネル (`ast.SEND`) の場合、以前は `p.print(token.CHAN, token.ARROW)` でしたが、新しいコードでは `p.print(token.CHAN, x.Arrow, token.ARROW)` となっています。
* `x.Arrow` は `ast.ChanType` ノードに格納された `<-` の正確な位置情報です。この位置情報を `p.print` に渡すことで、プリンターは `<-` 記号をソースコード上の元の正確な位置に(またはその近くに)出力できるようになります。これは、`go fmt` のようなツールがコードを整形する際に、元のソースコードのレイアウトをより忠実に再現するために役立ちます。
これらの変更は、Go言語のツールチェインがチャネル型をより正確に理解し、処理するための重要な改善であり、特にコードの整形、エラー報告、および静的解析の精度向上に貢献します。
## 関連リンク
* Go CL 6585063: [https://golang.org/cl/6585063](https://golang.org/cl/6585063)
## 参考にした情報源リンク
* Go言語の公式ドキュメント (チャネル): [https://go.dev/tour/concurrency/2](https://go.dev/tour/concurrency/2)
* 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)
* Go言語の `go/parser` パッケージ: [https://pkg.go.dev/go/parser](https://pkg.go.dev/go/parser)
* Go言語の `go/printer` パッケージ: [https://pkg.go.dev/go/printer](https://pkg.go.dev/go/printer)
* Abstract Syntax Tree (AST) の概念: [https://en.wikipedia.org/wiki/Abstract_syntax_tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree)