[インデックス 19617] ファイルの概要
このコミットは、Go言語のパーサーがレシーバ型における括弧の利用を許可するように変更するものです。具体的には、レシーバ型が括弧で囲まれた複雑な型表現を含む場合に、パーサーがそれを正しく解釈できるように修正が加えられました。これにより、Go言語のメソッド定義におけるレシーバ型の柔軟性が向上し、より多様な型表現が許容されるようになります。
コミット
- コミットハッシュ:
ef639b0936161bfb2a024acc05ec7beffcb08d56
- Author: Robert Griesemer gri@golang.org
- Date: Thu Jun 26 09:45:11 2014 -0700
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ef639b0936161bfb2a024acc05ec7beffcb08d56
元コミット内容
go/parser: permit parentheses in receiver types
Pending acceptance of CL 101500044
and adjustment of test/fixedbugs/bug299.go.
LGTM=adonovan
R=golang-codereviews, adonovan
CC=golang-codereviews
https://golang.org/cl/110160043
変更の背景
Go言語のパーサーは、メソッドのレシーバ型を解析する際に、特定の形式(例: *identifier
または identifier
)のみを期待していました。しかし、Go言語の型システムでは、括弧を使用して型表現をグループ化したり、複雑な型を定義したりすることが可能です(例: *(T)
はポインタ型 *T
と同じ意味を持つが、括弧で囲まれている)。
このコミット以前は、パーサーがレシーバ型に現れるこれらの括弧を適切に処理できず、構文エラーとして扱ってしまう可能性がありました。これは、Go言語の表現力を制限し、開発者がより柔軟な型定義をレシーバとして使用することを妨げていました。
この変更の背景には、Go言語のパーサーが、より複雑で、しかしGoの型システムとしては有効なレシーバ型表現を正しく認識し、抽象構文木(AST)を構築できるようにするという目的があります。コミットメッセージにある「Pending acceptance of CL 101500044」は、このパーサーの変更に関連する、より広範な言語設計の議論や、他の変更リスト(CL)の承認を指している可能性があります。これは、単なるバグ修正ではなく、Go言語の進化に伴うパーサーの堅牢性向上の一環と見なせます。
前提知識の解説
Go言語のメソッドとレシーバ
Go言語では、関数にレシーバを付与することで、その関数を「メソッド」として特定の型に関連付けることができます。レシーバは、メソッドが操作する値またはポインタを指定します。
例:
type MyType struct {
Value int
}
// 値レシーバのメソッド
func (m MyType) GetValue() int {
return m.Value
}
// ポインタレシーバのメソッド
func (m *MyType) SetValue(newValue int) {
m.Value = newValue
}
レシーバは (変数名 型)
の形式で定義され、この括弧はGo言語の構文上必須です。
Go言語の型表現と括弧
Go言語では、型を定義する際に括弧を使用することがあります。例えば、ポインタ型 *T
は、*(T)
と書くこともできます。これは、特に複雑な型(例: 関数型やインターフェース型)を定義する際に、可読性を高めたり、曖昧さを解消したりするために使用されます。
Go言語のパーサーとAST
Go言語のコンパイラツールチェーンにおいて、パーサー(go/parser
パッケージ)は非常に重要な役割を担っています。パーサーは、Goのソースコードを読み込み、その構文構造を解析して、抽象構文木(AST: Abstract Syntax Tree)と呼ばれるツリー構造のデータ表現に変換します。このASTは、その後のコンパイルプロセス(型チェック、コード生成など)の入力となります。
パーサーは、Go言語の文法規則に従ってソースコードを解析し、各構文要素(変数宣言、関数定義、式など)をASTノードとして表現します。もしソースコードが文法規則に違反している場合、パーサーはエラーを報告します。
go/ast
パッケージの Unparen
関数
go/ast
パッケージには、ASTノードを操作するためのユーティリティ関数が多数含まれています。その一つに Unparen(e ast.Expr) ast.Expr
があります。この関数は、与えられた式ノード e
が括弧で囲まれている場合、その括弧を取り除いた内側の式ノードを返します。これは、パーサーが構文上の括弧を無視して、その内側の実際の式を評価するために使用されます。例えば、((x + y))
のような式があった場合、Unparen
を繰り返し適用することで x + y
という本質的な式を取り出すことができます。
技術的詳細
このコミットの技術的な核心は、go/parser
パッケージ内の parseReceiver
関数が、レシーバ型を解析する際に、括弧で囲まれた型表現を適切に処理できるように変更された点にあります。
変更前は、parseReceiver
関数はレシーバ型を ["*"] identifier
の形式、つまりオプションのポインタ記号の後に識別子が続く形式であると厳密に解釈していました。これは、*T
や T
のような単純なレシーバ型には対応できますが、(*T)
のように括弧で囲まれた型表現には対応できませんでした。
変更後のコードでは、parseReceiver
関数内でレシーバ型を処理する際に、unparen
関数が複数回適用されています。具体的には、以下の行が変更されました。
- base := deref(recv.Type)
+ base := unparen(deref(unparen(recv.Type)))
この変更により、レシーバ型 recv.Type
が以下のように処理されます。
unparen(recv.Type)
: まず、レシーバ型全体を囲む可能性のある外側の括弧を取り除きます。例えば、レシーバ型が((T))
のような場合、最初のunparen
で(T)
になります。deref(...)
: 次に、ポインタ型の場合にそのポインタをデリファレンスします。これは、レシーバが*T
や(*T)
のようなポインタ型である場合に、その基底型(T
)を取得するために行われます。deref
関数は、ポインタ型*Expr
からExpr
を取り出す役割を担っていると推測されます。unparen(...)
: 最後に、デリファレンスされた型がさらに括弧で囲まれている可能性を考慮し、もう一度unparen
を適用します。例えば、*(T)
のような型がderef
された後に(T)
となった場合、この最後のunparen
でT
を取り出します。
この多段階の unparen
と deref
の適用により、パーサーは (T)
, (*T)
, *(T)
のような、括弧を含むより複雑なレシーバ型表現を正しく解析し、その基底となる識別子(T
)を抽出できるようになりました。これにより、パーサーはGo言語の型システムの柔軟性に対応し、より堅牢になりました。
また、short_test.go
に追加されたテストケース func ((T),) m() {}
, func ((*T),) m() {}
, func (*(T),) m() {}
は、この変更が意図した通りに、括弧で囲まれたレシーバ型が正しく解析されることを検証しています。これらのテストケースは、Go言語の通常のレシーバ構文とは異なる、より複雑な(あるいはエッジケース的な)括弧の利用パターンを示しており、パーサーがこれらのケースを適切に処理できるようになったことを示唆しています。
コアとなるコードの変更箇所
src/pkg/go/parser/parser.go
の parseReceiver
関数内の変更:
--- a/src/pkg/go/parser/parser.go
+++ b/src/pkg/go/parser/parser.go
@@ -2310,9 +2310,9 @@ func (p *parser) parseReceiver(scope *ast.Scope) *ast.FieldList {
return par
}
- // recv type must be of the form ["*"] identifier
+ // recv type must be of the form ["*"] identifier, possibly using parentheses
recv := par.List[0]
- base := deref(recv.Type)
+ base := unparen(deref(unparen(recv.Type)))
if _, isIdent := base.(*ast.Ident); !isIdent {
if _, isBad := base.(*ast.BadExpr); !isBad {
// only report error if it's a new one
src/pkg/go/parser/short_test.go
へのテストケースの追加:
--- a/src/pkg/go/parser/short_test.go
+++ b/src/pkg/go/parser/short_test.go
@@ -35,6 +35,9 @@ var valids = []string{
`package p; func f() { for _ = range "foo" + "bar" {} };`,\n \t`package p; func f() { var s []int; g(s[:], s[i:], s[:j], s[i:j], s[i:j:k], s[:j:k]) };`,\n \t`package p; var ( _ = (struct {*T}).m; _ = (interface {T}).m )`,\n+\t`package p; func ((T),) m() {}`,\n+\t`package p; func ((*T),) m() {}`,\n+\t`package p; func (*(T),) m() {}`,\n }\n \n func TestValid(t *testing.T) {
コアとなるコードの解説
変更された行 base := unparen(deref(unparen(recv.Type)))
は、レシーバ型 recv.Type
からその基底となる型(通常は識別子)を抽出するための処理です。
-
unparen(recv.Type)
:- これは、レシーバ型
recv.Type
の最も外側の括弧を取り除く最初のステップです。 - 例えば、
recv.Type
が((T))
であれば、この呼び出しの結果は(T)
になります。 - もし
recv.Type
が*T
やT
のように括弧で囲まれていなければ、そのままの型が返されます。
- これは、レシーバ型
-
deref(...)
:unparen
の結果がポインタ型(例:*T
や(T)
が*T
に変換された後)である場合、この関数はそのポインタをデリファレンスし、基底となる型(例:T
)を返します。- GoのASTにおいて、ポインタ型は
ast.StarExpr
として表現されることが多いため、deref
はast.StarExpr
からそのX
フィールド(ポインタが指す型)を取り出す処理を行うと考えられます。
-
unparen(...)
:deref
の結果がさらに括弧で囲まれている可能性を考慮し、再度unparen
を適用します。- 例えば、元のレシーバ型が
*(T)
であった場合、最初のunparen
は何もせず*(T)
のまま、deref
で(T)
になり、最後のunparen
でT
が抽出されます。
この三段階の処理により、パーサーはレシーバ型がどのような形式(単純な識別子、ポインタ、括弧で囲まれた型、またはそれらの組み合わせ)であっても、最終的にそのレシーバの「名前」となる識別子(ast.Ident
)を正確に特定できるようになります。これにより、if _, isIdent := base.(*ast.Ident); !isIdent
のチェックが、より広範な有効なレシーバ型に対して機能するようになります。
関連リンク
- Go CL 110160043: https://golang.org/cl/110160043 (このコミットに対応するGoの変更リスト)
参考にした情報源リンク
- Go言語のメソッドとレシーバに関する一般的な情報:
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEjOcCPHlfyOEiDBbDG7bSWeiOSL-jAVlAmgWYUMAJkRYzwP0r05SKLiFapRP8uDAeWNeuS5AgNUwLPojnbmEXBSLJPKrOziMjf8y-RdkbhtqLVtDm3bPxpTdPEuwL79JWuAr-TaverZKx3DGGuneAt9tDufIqjd6A=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFt8WZXlAZ8ATw7v8OrABoHuxrkIA5D9aj51Ocpl0fbv81s1n8yD9aNqbnfJ-F6usb08s3ku54r9cLOSlnbYuKQjLzooXKlqY_iDEl5lpt1CfFXLxZab5NFmxX4LXSj5ecOWR3ku7agT9znsusiDA==
go/ast
パッケージとUnparen
関数に関する情報:- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQE7KyTuyBC8PngGwUjHPIHn7dmHbk82AUK8mPWcVaOXnqH2hAzvPWZzik12WD0F5l3RN4N-FICanmEKFA2m7SkZo2eHSOhreZTlTHoQjI2NKhJ6xXFvv2sBrs5gj216rj4RzbbsoA8zd3if9dkpz3fe
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGz4r4BFQgAoQFsiqXi_r5fak-CAhmK38t5isxvXCG4wzJwpAN0z4YMHF-ziSOPeB0JXiDKPBEDYv8FHMDIphwT9uZk82S6ePkOOEcuSuC8npn4xA==
- Go ASTとパーサーの一般的な概念:
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEtRoTdNbrEg_X0Epf-i_hgTiavM71ZgwlHMoQvN9HDmOc0_yK-M4tCrd7RB44VcfKu02N3D8APvijncj3OpebR1CRJ041OfYJDjsJ6lbO4gI1gYy_y0w==
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEBzLDaAHNtBFVM5InpOICN3wowLTm3Cl_uYW_FLvYVQw8zWViLSew2rjyUbiI0vCEzMxtEqMNwGWGHPX0vT7_1_B8yH9EyWkxrgacCB2o5NKAnSHbxktZrPMFkOzpc4Qj0jhP77YJZBtEyAGtIpbebLZFTWxOt7XwRMsotb5vq7DLfkGFSjtywCA0jhC7V
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFVa7uC_oHZGpJAGSDcltJ4Asdo_zazihlNe2taq7M06kQi85yGsoHB_U0jx46LO9tRLU7Jiq9EyLLRnJAAk8GN7H4c06g5xsIGXZNGlpcMqAzp9TDeNrdpvpFQzLP5Qmpg5g==