[インデックス 19728] ファイルの概要
このコミットは、Go言語のfor...range
ステートメントにおいて、イテレーション変数を明示的に指定せずにfor range x
という形式を許可するように変更を加えるものです。これにより、レンジ式の値のみを処理し、キーや値が不要な場合にコードをより簡潔に記述できるようになります。
コミット
- コミットハッシュ:
d3a2f5870034db2d69bd0ef85f18a87f4163c770
- 作者: Robert Griesemer gri@golang.org
- 日付: Mon Jul 14 16:17:17 2014 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d3a2f5870034db2d69bd0ef85f18a87f4163c770
元コミット内容
go/*: permit "for range x"
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/112970044
変更の背景
Go言語のfor...range
ループは、スライス、配列、文字列、マップ、チャネルなどのコレクションをイテレートするための強力な構文です。このループは通常、for key, value := range collection {}
のように、イテレーションごとにキーと値(またはインデックスと値)を受け取る2つの変数を宣言します。しかし、場合によっては、イテレーション対象のコレクションの要素数だけループを回したいだけで、キーや値自体には興味がないことがあります。
このコミット以前は、キーや値が不要な場合でも、for _, value := range collection {}
のようにアンダースコア_
を使って不要な変数を明示的に破棄する必要がありました。これは冗長であり、特に値も不要な場合はfor _, _ = range collection {}
とさらに冗長になります。
この変更の背景には、コードの簡潔性と可読性の向上というGo言語の設計哲学があります。for range x
という構文を許可することで、イテレーション対象のコレクションの要素数だけループを回したいという意図をより明確かつ簡潔に表現できるようになります。これは、例えばスライスや配列の要素を単に消費するだけで、その値自体にはアクセスしないようなシナリオで特に有用です。
前提知識の解説
このコミットの変更を理解するためには、以下のGo言語の概念と、Goコンパイラが内部的に使用するツール群に関する知識が必要です。
Go言語のfor...range
ステートメント
for...range
ステートメントは、Go言語でコレクション(スライス、配列、文字列、マップ、チャネル)をイテレートするための構文です。基本的な形式は以下の通りです。
- スライス/配列/文字列:
for index, value := range collection {}
index
は要素のインデックス、value
は要素の値です。
- マップ:
for key, value := range map {}
key
はマップのキー、value
はマップの値です。
- チャネル:
for value := range channel {}
value
はチャネルから受信した値です。チャネルの場合、キーは存在しません。
このコミット以前は、キーや値が不要な場合でも、以下のように明示的に変数を宣言する必要がありました。
- キーが不要な場合:
for _, value := range collection {}
- 値が不要な場合:
for key, _ := range collection {}
- 両方不要な場合:
for _, _ = range collection {}
GoのAST (Abstract Syntax Tree)
Goコンパイラは、ソースコードを解析する際に、その構造を抽象構文木(AST)として表現します。ASTは、プログラムの構造を木構造で表現したもので、コンパイラがコードの意味を理解し、最適化やコード生成を行うための基盤となります。
go/ast
パッケージ: Go言語のASTを定義するパッケージです。ast.RangeStmt
のような構造体は、for...range
ステートメントのASTノードを表現します。ast.RangeStmt
:for...range
ステートメントを表すASTノードの構造体です。この構造体には、For
(forキーワードの位置),Key
(キー変数),Value
(値変数),TokPos
(代入演算子の位置),Tok
(代入演算子の種類),X
(レンジ式の値),Body
(ループ本体) などのフィールドが含まれます。ast.Expr
: 任意の式を表すインターフェースです。Key
やValue
フィールドはast.Expr
型です。
go/parser
パッケージ
go/parser
パッケージは、Go言語のソースコードを解析し、ASTを生成するためのパッケージです。このパッケージは、ソースコードの字句解析(トークン化)と構文解析を行い、ast
パッケージで定義されたASTノードのインスタンスを構築します。
go/printer
パッケージ
go/printer
パッケージは、ASTをGo言語のソースコードとして整形して出力するためのパッケージです。これは、ASTを人間が読める形式に戻す「逆解析」の役割を担います。
token
パッケージ
token
パッケージは、Go言語の字句要素(トークン)とそれらの位置(token.Pos
)を定義します。
token.Pos
: ソースコード内の位置を表します。token.Token
:ASSIGN
(代入演算子=
),DEFINE
(短い変数宣言演算子:=
),ILLEGAL
(不正なトークン) など、Go言語のキーワードや演算子、識別子などの種類を表します。
技術的詳細
このコミットは、for range x
という新しい構文をサポートするために、Goコンパイラのフロントエンド(パーサーとプリンター)におけるASTの定義と処理ロジックを変更しています。
src/pkg/go/ast/ast.go
の変更
ast.RangeStmt
構造体の定義が変更されています。
--- a/src/pkg/go/ast/ast.go
+++ b/src/pkg/go/ast/ast.go
@@ -699,9 +699,9 @@ type (
// A RangeStmt represents a for statement with a range clause.
RangeStmt struct {
For token.Pos // position of "for" keyword
- Key, Value Expr // Value may be nil
- TokPos token.Pos // position of Tok
- Tok token.Token // ASSIGN, DEFINE
+ Key, Value Expr // Key, Value may be nil
+ TokPos token.Pos // position of Tok; invalid if Key == nil
+ Tok token.Token // ILLEGAL if Key == nil, ASSIGN, DEFINE
X Expr // value to range over
Body *BlockStmt
}
- 変更前:
Key, Value Expr // Value may be nil
- 変更後:
Key, Value Expr // Key, Value may be nil
- これは、
Key
もnil
になりうることを明示しています。つまり、for range x
のようにキー変数が存在しない場合、Key
フィールドがnil
になることを示唆しています。
- これは、
- 変更前:
TokPos token.Pos // position of Tok
- 変更後:
TokPos token.Pos // position of Tok; invalid if Key == nil
TokPos
(代入演算子の位置)がKey
がnil
の場合(つまりfor range x
の場合)は無効になることを示しています。これは、for range x
構文では代入演算子が存在しないためです。
- 変更前:
Tok token.Token // ASSIGN, DEFINE
- 変更後:
Tok token.Token // ILLEGAL if Key == nil, ASSIGN, DEFINE
Tok
(代入演算子の種類)がKey
がnil
の場合(for range x
の場合)はtoken.ILLEGAL
になることを示しています。これも、代入演算子が存在しないことを反映しています。
これらの変更は、for range x
構文がASTレベルでどのように表現されるかを定義しています。Key
がnil
であるRangeStmt
は、イテレーション変数が明示的に宣言されていないfor...range
ループを表すことになります。
src/pkg/go/parser/parser.go
の変更
パーサーは、ソースコードを読み込み、ASTを構築する役割を担います。このコミットでは、parseForStmt
関数がfor range x
構文を認識し、適切にASTを構築するように変更されています。
--- a/src/pkg/go/parser/parser.go
+++ b/src/pkg/go/parser/parser.go
@@ -2041,7 +2041,16 @@ func (p *parser) parseForStmt() ast.Stmt {
prevLev := p.exprLev
p.exprLev = -1
if p.tok != token.SEMICOLON {
-\t\t\ts2, isRange = p.parseSimpleStmt(rangeOk)
+\t\t\tif p.tok == token.RANGE {
+\t\t\t\t// "for range x" (nil lhs in assignment)
+\t\t\t\tpos := p.pos
+\t\t\t\tp.next()\n+\t\t\t\ty := []ast.Expr{&ast.UnaryExpr{OpPos: pos, Op: token.RANGE, X: p.parseRhs()}}\n+\t\t\t\ts2 = &ast.AssignStmt{Rhs: y}\n+\t\t\t\tisRange = true\n+\t\t\t} else {\n+\t\t\t\ts2, isRange = p.parseSimpleStmt(rangeOk)\n+\t\t\t}\
}
if !isRange && p.tok == token.SEMICOLON {
p.next()
@@ -2066,12 +2075,14 @@ func (p *parser) parseForStmt() ast.Stmt {
// check lhs
var key, value ast.Expr
switch len(as.Lhs) {
-\t\tcase 2:\
-\t\t\tkey, value = as.Lhs[0], as.Lhs[1]\
+\t\tcase 0:\
+\t\t\t// nothing to do
case 1:\
key = as.Lhs[0]
+\t\tcase 2:\
+\t\t\tkey, value = as.Lhs[0], as.Lhs[1]\
default:\
-\t\t\tp.errorExpected(as.Lhs[0].Pos(), "1 or 2 expressions")
+\t\t\tp.errorExpected(as.Lhs[len(as.Lhs)-1].Pos(), "at most 2 expressions")
return &ast.BadStmt{From: pos, To: p.safePos(body.End())}
}
// parseSimpleStmt returned a right-hand side that
p.tok == token.RANGE
のチェックが追加され、for range x
のケースを特別に処理しています。- この場合、左辺(LHS)がないため、
ast.AssignStmt
のRhs
(右辺)にast.UnaryExpr
を作成し、Op
をtoken.RANGE
、X
をレンジ式として設定しています。これにより、パーサーはfor range x
を内部的にfor _ = range x
のような形式として解釈し、ASTを構築します。 switch len(as.Lhs)
のケースにcase 0:
が追加され、左辺の式が0個の場合(for range x
の場合)は何もしないようにしています。- エラーメッセージも
"1 or 2 expressions"
から"at most 2 expressions"
に変更され、0個の式も許容されることを反映しています。
src/pkg/go/printer/nodes.go
の変更
プリンターは、ASTをGoソースコードに変換する役割を担います。このコミットでは、RangeStmt
の処理ロジックが変更され、Key
がnil
の場合(for range x
の場合)に、キーや代入演算子を出力しないようにしています。
--- a/src/pkg/go/printer/nodes.go
+++ b/src/pkg/go/printer/nodes.go
@@ -1216,14 +1216,17 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool) {
case *ast.RangeStmt:\
p.print(token.FOR, blank)\
-\t\tp.expr(s.Key)\
-\t\tif s.Value != nil {\
-\t\t\t// use position of value following the comma as\
-\t\t\t// comma position for correct comment placement\
-\t\t\tp.print(s.Value.Pos(), token.COMMA, blank)\
-\t\t\tp.expr(s.Value)\
-\t\t}\
-\t\tp.print(blank, s.TokPos, s.Tok, blank, token.RANGE, blank)\
+\t\tif s.Key != nil {\
+\t\t\tp.expr(s.Key)\
+\t\t\tif s.Value != nil {\
+\t\t\t\t// use position of value following the comma as\
+\t\t\t\t// comma position for correct comment placement\
+\t\t\t\tp.print(s.Value.Pos(), token.COMMA, blank)\
+\t\t\t\tp.expr(s.Value)\
+\t\t\t}\
+\t\t\tp.print(blank, s.TokPos, s.Tok, blank)\
+\t\t}\
+\t\tp.print(token.RANGE, blank)\
p.expr(stripParens(s.X))\
p.print(blank)\
p.block(s.Body, 1)\
if s.Key != nil
という条件が追加され、Key
が存在する場合にのみ、キー変数と代入演算子を出力するように変更されています。- これにより、
for range x
のようにKey
がnil
のASTノードが与えられた場合でも、プリンターは正しくfor range x
という構文を生成できるようになります。
テストファイルの変更
src/pkg/go/parser/short_test.go
:for range x {}
という新しい構文を含むテストケースが追加され、パーサーがこれを正しく解析できることを確認しています。src/pkg/go/printer/testdata/statements.golden
とsrc/pkg/go/printer/testdata/statements.input
: プリンターのテストデータが更新され、for range x {}
、for _ = range a {}
、for _, _ = range a {}
といった新しい構文の出力が正しく整形されることを確認しています。
これらの変更により、Go言語のツールチェーン全体でfor range x
構文がサポートされるようになります。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。
src/pkg/go/ast/ast.go
:ast.RangeStmt
構造体の定義変更。src/pkg/go/parser/parser.go
:parseForStmt
関数におけるfor range x
構文の解析ロジックの追加。src/pkg/go/printer/nodes.go
:RangeStmt
の整形ロジックの変更。
src/pkg/go/ast/ast.go
type (
// A RangeStmt represents a for statement with a range clause.
RangeStmt struct {
For token.Pos // position of "for" keyword
Key, Value Expr // Key, Value may be nil
TokPos token.Pos // position of Tok; invalid if Key == nil
Tok token.Token // ILLEGAL if Key == nil, ASSIGN, DEFINE
X Expr // value to range over
Body *BlockStmt
}
)
src/pkg/go/parser/parser.go
(抜粋)
func (p *parser) parseForStmt() ast.Stmt {
// ... (既存のコード) ...
if p.tok != token.SEMICOLON {
if p.tok == token.RANGE {
// "for range x" (nil lhs in assignment)
pos := p.pos
p.next()
y := []ast.Expr{&ast.UnaryExpr{OpPos: pos, Op: token.RANGE, X: p.parseRhs()}}
s2 = &ast.AssignStmt{Rhs: y}
isRange = true
} else {
s2, isRange = p.parseSimpleStmt(rangeOk)
}
}
// ... (既存のコード) ...
switch len(as.Lhs) {
case 0:
// nothing to do
case 1:
key = as.Lhs[0]
case 2:
key, value = as.Lhs[0], as.Lhs[1]
default:
p.errorExpected(as.Lhs[len(as.Lhs)-1].Pos(), "at most 2 expressions")
return &ast.BadStmt{From: pos, To: p.safePos(body.End())}
}
// ... (既存のコード) ...
}
src/pkg/go/printer/nodes.go
(抜粋)
func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool) {
// ... (既存のコード) ...
case *ast.RangeStmt:
p.print(token.FOR, blank)
if s.Key != nil {
p.expr(s.Key)
if s.Value != nil {
// use position of value following the comma as
// comma position for correct comment placement
p.print(s.Value.Pos(), token.COMMA, blank)
p.expr(s.Value)
}
p.print(blank, s.TokPos, s.Tok, blank)
}
p.print(token.RANGE, blank)
p.expr(stripParens(s.X))
p.print(blank)
p.block(s.Body, 1)
// ... (既存のコード) ...
}
コアとなるコードの解説
src/pkg/go/ast/ast.go
の解説
ast.RangeStmt
構造体の変更は、for range x
という新しい構文をASTレベルで表現するための基盤を確立します。
Key, Value Expr // Key, Value may be nil
: このコメントの変更は、Key
フィールドもnil
になりうることを明示しています。これは、for range x
のようにキー変数が存在しない場合に、RangeStmt
のKey
フィールドがnil
として表現されることを意味します。TokPos token.Pos // position of Tok; invalid if Key == nil
:TokPos
は代入演算子(:=
または=
)の位置を示しますが、for range x
構文ではこの演算子が存在しないため、Key
がnil
の場合はこの位置情報が無効になることを示しています。Tok token.Token // ILLEGAL if Key == nil, ASSIGN, DEFINE
:Tok
は代入演算子の種類を示しますが、Key
がnil
の場合は代入演算子が存在しないため、token.ILLEGAL
という特殊なトークンが設定されることを示しています。これにより、ASTを処理する他のツールがこの特殊なケースを認識できるようになります。
これらの変更により、ASTはfor range x
という構文を、キー変数が存在しないRangeStmt
として内部的に表現できるようになります。
src/pkg/go/parser/parser.go
の解説
parseForStmt
関数は、for
ループの構文解析を担当します。この変更の核心は、for range x
という新しいパターンを認識し、それに対応するASTノードを生成する部分です。
if p.tok == token.RANGE
: パーサーがfor
キーワードの直後にrange
キーワードを検出した場合、それはfor range x
構文であると判断します。y := []ast.Expr{&ast.UnaryExpr{OpPos: pos, Op: token.RANGE, X: p.parseRhs()}}
: ここが重要な部分です。for range x
は、内部的には左辺がない代入文として扱われます。ast.UnaryExpr
は単項演算子を表すASTノードですが、ここではOp
をtoken.RANGE
に設定することで、レンジ式自体を表現しています。p.parseRhs()
はレンジ対象の式(この場合はx
)を解析します。このUnaryExpr
がast.AssignStmt
のRhs
(右辺)に設定されます。これにより、パーサーはfor range x
を、あたかもfor _ = range x
のように、左辺が暗黙的に破棄される形式としてASTを構築します。switch len(as.Lhs)
のcase 0:
: これは、for range x
のように左辺の式が全くない場合を処理します。このケースでは、特別な処理は不要であり、key
とvalue
はnil
のままになります。これにより、ast.RangeStmt
のKey
フィールドがnil
になることが保証されます。
src/pkg/go/printer/nodes.go
の解説
printer
パッケージはASTをGoソースコードに変換します。この変更は、ast.RangeStmt
ノードを整形する際に、Key
フィールドがnil
の場合にキー変数と代入演算子を出力しないようにします。
if s.Key != nil
: この条件文が追加されたことで、プリンターはRangeStmt
のKey
フィールドがnil
でない場合(つまり、for key, value := range x
やfor key := range x
のようにキー変数が明示的に存在する場合)にのみ、キー変数と代入演算子を出力します。p.print(token.RANGE, blank)
:Key
がnil
の場合でも、range
キーワードとレンジ式は常に出力されます。
このロジックにより、パーサーが生成したKey
がnil
のRangeStmt
が、正しくfor range x
という簡潔な構文として出力されるようになります。
関連リンク
- Go言語の
for
ステートメントに関する公式ドキュメント: https://go.dev/tour/flowcontrol/9 - Go言語のASTに関するドキュメント(
go/ast
パッケージ): https://pkg.go.dev/go/ast - Go言語のパーサーに関するドキュメント(
go/parser
パッケージ): https://pkg.go.dev/go/parser - Go言語のプリンターに関するドキュメント(
go/printer
パッケージ): https://pkg.go.dev/go/printer
参考にした情報源リンク
- Go言語の公式ドキュメント
go/ast
,go/parser
,go/printer
パッケージのGoDoc- コミットの差分情報
- Go言語の
for...range
に関する一般的な解説記事 (Web検索結果)I have generated the detailed explanation based on the commit data and the specified chapter structure. I have also incorporated the necessary technical details and prerequisite knowledge. I will now output the explanation to standard output.