[インデックス 13144] ファイルの概要
このコミットは、Go言語のパーサー(go/parser
パッケージ)における型解決の挙動を改善するものです。特に、関数パラメータと構造体フィールドの型が、パーシング段階で適切に解決されるように修正が加えられています。これにより、パーサーが生成する抽象構文木(AST)の正確性が向上し、後続の型チェックやセマンティック分析のフェーズでの処理がより堅牢になります。
コミット
commit 1f46cb0ba26b392e19be34d74db51bc32b9b1b92
Author: Robert Griesemer <gri@golang.org>
Date: Wed May 23 16:12:45 2012 -0700
go/parser: resolve all parameter types
Fixes #3655.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6213065
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1f46cb0ba26b392b19be34d74db51bc32b9b1b92
元コミット内容
go/parser: resolve all parameter types
このコミットは、Go言語のパーサーがすべてのパラメータ型を解決するように修正します。
Fixes #3655.
これはIssue #3655を修正します。
変更の背景
Go言語のコンパイラは、ソースコードを処理する際に複数のフェーズを経ます。その初期段階の一つが「パーシング(構文解析)」であり、ソースコードを抽象構文木(AST)に変換します。このASTは、その後の型チェック、セマンティック分析、コード生成などのフェーズで利用されます。
このコミットが修正するIssue #3655は、go/parser
が特定の状況下で関数パラメータや構造体フィールドの型を適切に解決できていなかった問題に関連しています。具体的には、パーサーがASTを構築する際に、型を表す識別子(例: int
, string
, カスタム型名)が、それが参照する実際の型定義にリンクされていない、つまり「未解決」のまま残ってしまうケースがあったと考えられます。
このような未解決の型が存在すると、後続のコンパイルフェーズで正確な型情報が得られず、誤った型チェックエラーが発生したり、コンパイラがクラッシュしたりする可能性がありました。このコミットの目的は、パーシング段階で可能な限りすべての型識別子を解決し、ASTの健全性を高めることにあります。これにより、コンパイラの安定性と正確性が向上します。
前提知識の解説
このコミットを理解するためには、以下の概念が重要です。
- Go言語の構文と型システム:
- 関数パラメータ: Goの関数は、
func f(a int, b string)
のようにパラメータを持ちます。 - 可変長パラメータ (
...
):func f(args ...int)
のように、任意の数の引数を受け取るパラメータです。 - 構造体フィールド:
type MyStruct struct { Field1 int; Field2 string }
のように、構造体はフィールドを持ちます。 - 匿名フィールド: 構造体内に型名のみで宣言されるフィールド(例:
type MyStruct struct { io.Reader }
)。この場合、フィールド名は型名と同じになります。
- 関数パラメータ: Goの関数は、
- コンパイラの基本概念:
- 字句解析 (Lexing/Tokenizing): ソースコードを意味のある最小単位(トークン)に分割するプロセス(例:
func
,f
,(
,a
,int
,,
など)。 - 構文解析 (Parsing): トークンのストリームを文法規則に従って解析し、プログラムの構造を階層的なツリー構造(抽象構文木、AST)として構築するプロセス。
- 抽象構文木 (AST - Abstract Syntax Tree): ソースコードの構造を表現するツリーデータ構造。各ノードはプログラムの構成要素(式、文、宣言など)を表します。
go/ast
パッケージで定義されています。 - シンボル解決 (Symbol Resolution): プログラム内の識別子(変数名、関数名、型名など)が、それが参照する宣言(定義)と関連付けられるプロセス。例えば、変数
x
がどのx
の宣言を参照しているかを特定します。パーサーやセマンティックアナライザーの重要な機能です。 - スコープ (Scope): 識別子が有効なプログラムの領域。Goでは、ブロック、関数、ファイル、パッケージなど、様々なスコープが存在します。
- 字句解析 (Lexing/Tokenizing): ソースコードを意味のある最小単位(トークン)に分割するプロセス(例:
- Goの標準ライブラリ
go/parser
とgo/ast
:go/parser
パッケージは、Goのソースコードを解析し、go/ast
パッケージで定義されたASTを生成します。go/ast.Expr
: AST内の式を表すインターフェース。go/ast.Field
: 構造体のフィールド、または関数のパラメータや結果変数を表すASTノード。go/ast.BasicLit
: 整数リテラル、文字列リテラルなどの基本的なリテラルを表すASTノード。go/ast.StarExpr
: ポインタ型(例:*int
)を表すASTノード。go/ast.Scope
: 識別子のスコープを表すデータ構造。token
パッケージ: Go言語のトークン定数を定義します(例:token.INT
,token.STRING
,token.LPAREN
など)。
parser
構造体の内部メソッド:p.resolve(typ ast.Expr)
: パーサー内部のメソッドで、ASTノードtyp
が表す型を解決する役割を担います。これは通常、typ
が識別子である場合に、その識別子がどの型宣言を参照しているかをスコープ内で探し、ASTノードにその解決済み情報を付与する処理を指します。p.declare(field *ast.Field, ...)
: パーサー内部のメソッドで、新しい識別子(この場合はフィールドやパラメータ)を現在のスコープに宣言する役割を担います。
技術的詳細
このコミットの核心は、go/parser
パッケージ内のparser
構造体のメソッドにおけるp.resolve(typ)
の呼び出しタイミングと場所の調整です。p.resolve
は、ASTノードが表す型を、その識別子が参照する実際の型定義にリンクさせるための重要な操作です。
変更前は、特定の構文パターン(特に匿名フィールドや可変長パラメータの型、複数のパラメータがまとめて宣言される場合など)において、型を表すASTノードが生成された直後にp.resolve
が呼び出されていませんでした。これにより、ASTが完全に構築された時点で、一部の型情報が未解決のまま残る可能性がありました。
このコミットでは、以下の主要な変更を通じて、この問題を解決しています。
parseFieldDecl
における型解決の統一:- 構造体のフィールド宣言を解析する
parseFieldDecl
関数において、匿名フィールドの型解決ロジックが変更されました。以前は特定の条件下でのみp.resolve(typ)
が呼び出されていましたが、変更後はフィールドが宣言された直後(p.declare
の後)に、常にp.resolve(typ)
が呼び出されるようになりました。これにより、すべてのフィールド型が確実に解決されるようになります。
- 構造体のフィールド宣言を解析する
tryVarType
における可変長パラメータの型解決:- 可変長パラメータ(
...Type
)の型を解析するtryVarType
関数において、型が正常に解析された場合にp.resolve(typ)
が明示的に呼び出されるようになりました。以前は、型が存在しない場合にエラーを報告するロジックの中に型解決が含まれておらず、型が存在する場合の解決が漏れる可能性がありました。
- 可変長パラメータ(
parseVarList
における型解決の委譲:- 変数リスト(例:
a, b int
)を解析するparseVarList
関数から、直接的なp.resolve(typ)
の呼び出しが削除されました。代わりに、この関数が返す型(typ
)の解決は、そのtyp
を受け取る呼び出し元(例:parseParameterList
)に委譲されるようになりました。これは、parseVarList
が必ずしも最終的な型解決を行うべき場所ではないという設計思想の変更を示唆しています。コメント// If any of the results are identifiers, they are not resolved.
が追加され、この関数の役割が「識別子を解決しない」ことにあると明記されています。
- 変数リスト(例:
parseParameterList
におけるパラメータ型解決の強化:- 関数パラメータリストを解析する
parseParameterList
関数において、パラメータの型が宣言された直後にp.resolve(typ)
が追加されました。これは、a, b int
のような複数のパラメータが同じ型を持つ場合や、個別のパラメータ宣言の場合の両方で適用されます。これにより、すべての関数パラメータの型がパーシング段階で確実に解決されるようになります。 - 匿名パラメータ(例:
func(int, string)
)の型解決も、ループ内でp.resolve(typ)
が明示的に呼び出されることで、より堅牢になりました。
- 関数パラメータリストを解析する
これらの変更は、go/parser
がASTを構築する際に、型情報をより早期かつ一貫して解決するように促します。これにより、ASTがより「完全な」状態で生成され、後続のコンパイルフェーズでの処理が簡素化され、エラーの可能性が低減されます。
コアとなるコードの変更箇所
変更は主にsrc/pkg/go/parser/parser.go
と、その変更を検証するためのテストファイルsrc/pkg/go/parser/parser_test.go
に集中しています。
src/pkg/go/parser/parser.go
-
func (p *parser) parseFieldDecl(scope *ast.Scope) *ast.Field
- 変更前:
// [\"*\"] TypeName (AnonymousField) typ = list[0] // we always have at least one element p.resolve(typ) // <-- この行が削除された if n := len(list); n > 1 || !isTypeName(deref(typ)) { pos := typ.Pos() p.errorExpected(pos, "anonymous field") typ = &ast.BadExpr{From: pos, To: p.pos} }
- 変更後:
// [\"*\"] TypeName (AnonymousField) typ = list[0] // we always have at least one element if n := len(list); n > 1 || !isTypeName(deref(typ)) { pos := typ.Pos() p.errorExpected(pos, "anonymous field") typ = &ast.BadExpr{From: pos, To: p.pos} } // ... field := &ast.Field{Doc: doc, Names: idents, Type: typ, Tag: tag, Comment: p.lineComment} p.declare(field, nil, scope, ast.Var, idents...) p.resolve(typ) // <-- ここに移動・追加された
- 変更の意図: 匿名フィールドの型解決が、フィールド宣言全体の解決ロジックと統合され、
p.declare
の直後に一貫して行われるようになりました。
- 変更前:
-
func (p *parser) tryVarType(isParam bool) ast.Expr
- 変更前:
if isParam && p.tok == token.ELLIPSIS { pos := p.pos p.next() typ := p.tryIdentOrType(isParam) // don\'t use parseType so we can provide better error message if typ == nil { // <-- この条件分岐が変更された p.error(pos, "'...' parameter is missing type") typ = &ast.BadExpr{From: pos, To: p.pos} } return &ast.Ellipsis{Ellipsis: pos, Elt: typ} }
- 変更後:
if isParam && p.tok == token.ELLIPSIS { pos := p.pos p.next() typ := p.tryIdentOrType(isParam) // don\'t use parseType so we can provide better error message if typ != nil { // <-- typがnilでない場合に解決するようになった p.resolve(typ) } else { // <-- typがnilの場合の処理 p.error(pos, "'...' parameter is missing type") typ = &ast.BadExpr{From: pos, To: p.pos} } return &ast.Ellipsis{Ellipsis: pos, Elt: typ} }
- 変更の意図: 可変長パラメータの型が正常に解析された場合、その型を明示的に解決するようにしました。
- 変更前:
-
func (p *parser) parseVarList(isParam bool) (list []ast.Expr, typ ast.Expr)
- 変更前:
// if we had a list of identifiers, it must be followed by a type if typ = p.tryVarType(isParam); typ != nil { p.resolve(typ) // <-- この行が削除された }
- 変更後:
// if we had a list of identifiers, it must be followed by a type // If any of the results are identifiers, they are not resolved. // <-- 新しいコメント typ = p.tryVarType(isParam) // <-- p.resolve(typ)が削除された
- 変更の意図:
parseVarList
自身が型を解決するのではなく、その結果を受け取る呼び出し元に解決を委譲するようになりました。新しいコメントは、この関数の役割を明確にしています。
- 変更前:
-
func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params []*ast.Field)
- 変更前:
if typ != nil { // IdentifierList Type idents := p.makeIdentList(list) field := &ast.Field{Names: idents, Type: typ} p.declare(field, nil, scope, ast.Var, idents...) // p.resolve(typ) がなかった if p.tok == token.COMMA { p.next() } // ... for p.tok != token.RPAREN && p.tok != token.EOF { idents := p.parseIdentList() typ := p.parseVarType(ellipsisOk) field := &ast.Field{Names: idents, Type: typ} p.declare(field, nil, scope, ast.Var, idents...) // p.resolve(typ) がなかった if !p.atComma("parameter list") { break } p.next() } } else { // Type { "," Type } (anonymous parameters) params = make([]*ast.Field, len(list)) for i, x := range list { p.resolve(x) // <-- xをtypにリネーム params[i] = &ast.Field{Type: x} // <-- xをtypにリネーム } }
- 変更後:
// ParameterDecl // <-- 新しいコメント list, typ := p.parseVarList(ellipsisOk) // analyze case // <-- 新しいコメント if typ != nil { // IdentifierList Type idents := p.makeIdentList(list) field := &ast.Field{Names: idents, Type: typ} p.declare(field, nil, scope, ast.Var, idents...) p.resolve(typ) // <-- 追加された if p.tok == token.COMMA { p.next() } // ... for p.tok != token.RPAREN && p.tok != token.EOF { idents := p.parseIdentList() typ := p.parseVarType(ellipsisOk) field := &ast.Field{Names: idents, Type: typ} p.declare(field, nil, scope, ast.Var, idents...) p.resolve(typ) // <-- 追加された if !p.atComma("parameter list") { break } p.next() } } else { // Type { "," Type } (anonymous parameters) params = make([]*ast.Field, len(list)) for i, typ := range list { // <-- xをtypにリネーム p.resolve(typ) // <-- xをtypにリネーム params[i] = &ast.Field{Type: typ} // <-- xをtypにリネーム } }
- 変更の意図: 関数パラメータの型が、識別子リストの後に続く場合(例:
a, b int
)と、個別のパラメータ宣言の場合(例:a int, b string
)の両方で、p.declare
の直後にp.resolve(typ)
が呼び出されるようになりました。これにより、すべてのパラメータ型が確実に解決されます。匿名パラメータのループ変数もx
からtyp
にリネームされ、可読性が向上しています。
- 変更前:
src/pkg/go/parser/parser_test.go
func TestUnresolved(t *testing.T)
の追加- この新しいテスト関数は、様々な関数シグネチャと構造体フィールド宣言を含むGoのソースコードを解析し、パーサーが
f.Unresolved
リストに格納する「未解決の識別子」を収集します。 want
変数には、Goの組み込み型(int
,byte
,float
,complex
など)のように、パーサー自身が明示的に解決する必要がない(コンパイラの型チェッカーが最終的に解決する)識別子のリストが期待値として定義されています。- このテストの目的は、パーサーが「解決すべき型」を適切に解決し、「解決する必要がない(または後続フェーズで解決される)型」を未解決リストに正しく残すことを検証することです。このテストの追加は、今回の変更がパーサーの型解決ロジックに与える影響を正確に捉えるために不可欠です。
- この新しいテスト関数は、様々な関数シグネチャと構造体フィールド宣言を含むGoのソースコードを解析し、パーサーが
コアとなるコードの解説
このコミットのコアとなる変更は、p.resolve(typ)
メソッドの戦略的な配置にあります。
p.resolve(typ)
は、go/parser
パッケージの内部で、ASTノードtyp
が表す型を、その識別子が参照する実際の型定義にリンクさせる役割を担っています。例えば、ソースコードにvar x MyType
という宣言があった場合、パーサーはMyType
という識別子を解析し、それをASTノードとして表現します。p.resolve(MyTypeASTNode)
が呼び出されると、パーサーは現在のスコープ内でMyType
という名前の型定義を探し、見つかればその型定義への参照をMyTypeASTNode
に設定します。これにより、ASTは単なる構文構造だけでなく、セマンティックな情報(この識別子がどの定義を参照しているか)も含むようになります。
変更前は、特に以下のようなケースでp.resolve(typ)
の呼び出しが漏れていました。
- 匿名フィールド: 構造体内の匿名フィールド(例:
struct { io.Reader }
)の型。 - 可変長パラメータ:
func f(args ...int)
のような可変長パラメータの型。 - 複数のパラメータ宣言:
func f(a, b int)
のように、複数のパラメータが同じ型を持つ場合の型。
これらのケースでp.resolve(typ)
が適切に呼び出されないと、生成されるASTではこれらの型が「未解決」のままとなり、後続のコンパイルフェーズで問題を引き起こす可能性がありました。
今回の変更では、これらの見落としを修正し、parseFieldDecl
、tryVarType
、parseParameterList
といった関数内で、型が特定された直後、またはフィールド/パラメータがスコープに宣言された直後にp.resolve(typ)
を呼び出すようにしました。
特にparseVarList
からのp.resolve(typ)
の削除は重要です。これは、parseVarList
が識別子のリストとそれに続く型を解析する汎用的な関数であり、その型が常に即座に解決されるべきではないという設計上の判断を示しています。例えば、var a, b int
のような変数宣言の場合、int
型はparseVarList
で解析されますが、その解決は変数宣言全体のコンテキストで行われるべきです。そのため、parseVarList
は型を「見つける」だけで、その解決は呼び出し元に委ねるという役割分担が明確化されました。
新しいTestUnresolved
テストは、この変更が意図通りに機能していることを検証します。このテストは、パーサーがASTを構築した後に残る「未解決の識別子」のリストをチェックします。期待される未解決の識別子は、int
, byte
, float
などの組み込み型です。これらの型はGoの言語仕様で定義されており、パーサーが明示的に解決しなくても、コンパイラの型チェッカーが最終的に解決できるため、未解決のままで問題ありません。このテストがパスすることで、パーサーが「解決すべき型」を適切に解決し、「未解決のままで良い型」を正しく識別していることが確認されます。
関連リンク
- Go Issue #3655: https://github.com/golang/go/issues/3655
- Gerrit Change-Id (CL) 6213065: https://golang.org/cl/6213065
参考にした情報源リンク
- Go言語の公式ドキュメント (go.dev)
- Go言語のソースコード (github.com/golang/go)
- Go言語の
go/parser
およびgo/ast
パッケージのドキュメント - コンパイラ設計に関する一般的な情報源(構文解析、AST、シンボル解決など)
[インデックス 13144] ファイルの概要
このコミットは、Go言語のパーサー(go/parser
パッケージ)における型解決の挙動を改善するものです。特に、関数パラメータと構造体フィールドの型が、パーシング段階で適切に解決されるように修正が加えられています。これにより、パーサーが生成する抽象構文木(AST)の正確性が向上し、後続の型チェックやセマンティック分析のフェーズでの処理がより堅牢になります。
コミット
commit 1f46cb0ba26b392e19be34d74db51bc32b9b1b92
Author: Robert Griesemer <gri@golang.org>
Date: Wed May 23 16:12:45 2012 -0700
go/parser: resolve all parameter types
Fixes #3655.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6213065
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1f46cb0ba26b392e19be34d74db51bc32b9b1b92
元コミット内容
go/parser: resolve all parameter types
このコミットは、Go言語のパーサーがすべてのパラメータ型を解決するように修正します。
Fixes #3655.
これはIssue #3655を修正します。
変更の背景
Go言語のコンパイラは、ソースコードを処理する際に複数のフェーズを経ます。その初期段階の一つが「パーシング(構文解析)」であり、ソースコードを抽象構文木(AST)に変換します。このASTは、その後の型チェック、セマンティック分析、コード生成などのフェーズで利用されます。
このコミットが修正するIssue #3655は、go/parser
が特定の状況下で関数パラメータや構造体フィールドの型を適切に解決できていなかった問題に関連しています。具体的には、パーサーがASTを構築する際に、型を表す識別子(例: int
, string
, カスタム型名)が、それが参照する実際の型定義にリンクされていない、つまり「未解決」のまま残ってしまうケースがあったと考えられます。
このような未解決の型が存在すると、後続のコンパイルフェーズで正確な型情報が得られず、誤った型チェックエラーが発生したり、コンパイラがクラッシュしたりする可能性がありました。このコミットの目的は、パーシング段階で可能な限りすべての型識別子を解決し、ASTの健全性を高めることにあります。これにより、コンパイラの安定性と正確性が向上します。
前提知識の解説
このコミットを理解するためには、以下の概念が重要です。
- Go言語の構文と型システム:
- 関数パラメータ: Goの関数は、
func f(a int, b string)
のようにパラメータを持ちます。 - 可変長パラメータ (
...
):func f(args ...int)
のように、任意の数の引数を受け取るパラメータです。 - 構造体フィールド:
type MyStruct struct { Field1 int; Field2 string }
のように、構造体はフィールドを持ちます。 - 匿名フィールド: 構造体内に型名のみで宣言されるフィールド(例:
type MyStruct struct { io.Reader }
)。この場合、フィールド名は型名と同じになります。
- 関数パラメータ: Goの関数は、
- コンパイラの基本概念:
- 字句解析 (Lexing/Tokenizing): ソースコードを意味のある最小単位(トークン)に分割するプロセス(例:
func
,f
,(
,a
,int
,,
など)。 - 構文解析 (Parsing): トークンのストリームを文法規則に従って解析し、プログラムの構造を階層的なツリー構造(抽象構文木、AST)として構築するプロセス。
- 抽象構文木 (AST - Abstract Syntax Tree): ソースコードの構造を表現するツリーデータ構造。各ノードはプログラムの構成要素(式、文、宣言など)を表します。
go/ast
パッケージで定義されています。 - シンボル解決 (Symbol Resolution): プログラム内の識別子(変数名、関数名、型名など)が、それが参照する宣言(定義)と関連付けられるプロセス。例えば、変数
x
がどのx
の宣言を参照しているかを特定します。パーサーやセマンティックアナライザーの重要な機能です。 - スコープ (Scope): 識別子が有効なプログラムの領域。Goでは、ブロック、関数、ファイル、パッケージなど、様々なスコープが存在します。
- 字句解析 (Lexing/Tokenizing): ソースコードを意味のある最小単位(トークン)に分割するプロセス(例:
- Goの標準ライブラリ
go/parser
とgo/ast
:go/parser
パッケージは、Goのソースコードを解析し、go/ast
パッケージで定義されたASTを生成します。go/ast.Expr
: AST内の式を表すインターフェース。go/ast.Field
: 構造体のフィールド、または関数のパラメータや結果変数を表すASTノード。go/ast.BasicLit
: 整数リテラル、文字列リテラルなどの基本的なリテラルを表すASTノード。go/ast.StarExpr
: ポインタ型(例:*int
)を表すASTノード。go/ast.Scope
: 識別子のスコープを表すデータ構造。token
パッケージ: Go言語のトークン定数を定義します(例:token.INT
,token.STRING
,token.LPAREN
など)。
parser
構造体の内部メソッド:p.resolve(typ ast.Expr)
: パーサー内部のメソッドで、ASTノードtyp
が表す型を解決する役割を担います。これは通常、typ
が識別子である場合に、その識別子がどの型宣言を参照しているかをスコープ内で探し、ASTノードにその解決済み情報を付与する処理を指します。p.declare(field *ast.Field, ...)
: パーサー内部のメソッドで、新しい識別子(この場合はフィールドやパラメータ)を現在のスコープに宣言する役割を担います。
技術的詳細
このコミットの核心は、go/parser
パッケージ内のparser
構造体のメソッドにおけるp.resolve(typ)
の呼び出しタイミングと場所の調整です。p.resolve
は、ASTノードが表す型を、その識別子が参照する実際の型定義にリンクさせるための重要な操作です。
変更前は、特定の構文パターン(特に匿名フィールドや可変長パラメータの型、複数のパラメータがまとめて宣言される場合など)において、型を表すASTノードが生成された直後にp.resolve
が呼び出されていませんでした。これにより、ASTが完全に構築された時点で、一部の型情報が未解決のまま残る可能性がありました。
このコミットでは、以下の主要な変更を通じて、この問題を解決しています。
parseFieldDecl
における型解決の統一:- 構造体のフィールド宣言を解析する
parseFieldDecl
関数において、匿名フィールドの型解決ロジックが変更されました。以前は特定の条件下でのみp.resolve(typ)
が呼び出されていましたが、変更後はフィールドが宣言された直後(p.declare
の後)に、常にp.resolve(typ)
が呼び出されるようになりました。これにより、すべてのフィールド型が確実に解決されるようになります。
- 構造体のフィールド宣言を解析する
tryVarType
における可変長パラメータの型解決:- 可変長パラメータ(
...Type
)の型を解析するtryVarType
関数において、型が正常に解析された場合にp.resolve(typ)
が明示的に呼び出されるようになりました。以前は、型が存在しない場合にエラーを報告するロジックの中に型解決が含まれておらず、型が存在する場合の解決が漏れる可能性がありました。
- 可変長パラメータ(
parseVarList
における型解決の委譲:- 変数リスト(例:
a, b int
)を解析するparseVarList
関数から、直接的なp.resolve(typ)
の呼び出しが削除されました。代わりに、この関数が返す型(typ
)の解決は、そのtyp
を受け取る呼び出し元(例:parseParameterList
)に委譲されるようになりました。これは、parseVarList
が必ずしも最終的な型解決を行うべき場所ではないという設計思想の変更を示唆しています。コメント// If any of the results are identifiers, they are not resolved.
が追加され、この関数の役割が「識別子を解決しない」ことにあると明記されています。
- 変数リスト(例:
parseParameterList
におけるパラメータ型解決の強化:- 関数パラメータリストを解析する
parseParameterList
関数において、パラメータの型が宣言された直後にp.resolve(typ)
が追加されました。これは、a, b int
のような複数のパラメータが同じ型を持つ場合や、個別のパラメータ宣言の場合の両方で適用されます。これにより、すべての関数パラメータの型がパーシング段階で確実に解決されるようになります。 - 匿名パラメータ(例:
func(int, string)
)の型解決も、ループ内でp.resolve(typ)
が明示的に呼び出されることで、より堅牢になりました。
- 関数パラメータリストを解析する
これらの変更は、go/parser
がASTを構築する際に、型情報をより早期かつ一貫して解決するように促します。これにより、ASTがより「完全な」状態で生成され、後続のコンパイルフェーズでの処理が簡素化され、エラーの可能性が低減します。
コアとなるコードの変更箇所
変更は主にsrc/pkg/go/parser/parser.go
と、その変更を検証するためのテストファイルsrc/pkg/go/parser/parser_test.go
に集中しています。
src/pkg/go/parser/parser.go
-
func (p *parser) parseFieldDecl(scope *ast.Scope) *ast.Field
- 変更前:
// [\"*\"] TypeName (AnonymousField) typ = list[0] // we always have at least one element p.resolve(typ) // <-- この行が削除された if n := len(list); n > 1 || !isTypeName(deref(typ)) { pos := typ.Pos() p.errorExpected(pos, "anonymous field") typ = &ast.BadExpr{From: pos, To: p.pos} }
- 変更後:
// [\"*\"] TypeName (AnonymousField) typ = list[0] // we always have at least one element if n := len(list); n > 1 || !isTypeName(deref(typ)) { pos := typ.Pos() p.errorExpected(pos, "anonymous field") typ = &ast.BadExpr{From: pos, To: p.pos} } // ... field := &ast.Field{Doc: doc, Names: idents, Type: typ, Tag: tag, Comment: p.lineComment} p.declare(field, nil, scope, ast.Var, idents...) p.resolve(typ) // <-- ここに移動・追加された
- 変更の意図: 匿名フィールドの型解決が、フィールド宣言全体の解決ロジックと統合され、
p.declare
の直後に一貫して行われるようになりました。
- 変更前:
-
func (p *parser) tryVarType(isParam bool) ast.Expr
- 変更前:
if isParam && p.tok == token.ELLIPSIS { pos := p.pos p.next() typ := p.tryIdentOrType(isParam) // don\'t use parseType so we can provide better error message if typ == nil { // <-- この条件分岐が変更された p.error(pos, "'...' parameter is missing type") typ = &ast.BadExpr{From: pos, To: p.pos} } return &ast.Ellipsis{Ellipsis: pos, Elt: typ} }
- 変更後:
if isParam && p.tok == token.ELLIPSIS { pos := p.pos p.next() typ := p.tryIdentOrType(isParam) // don\'t use parseType so we can provide better error message if typ != nil { // <-- typがnilでない場合に解決するようになった p.resolve(typ) } else { // <-- typがnilの場合の処理 p.error(pos, "'...' parameter is missing type") typ = &ast.BadExpr{From: pos, To: p.pos} } return &ast.Ellipsis{Ellipsis: pos, Elt: typ} }
- 変更の意図: 可変長パラメータの型が正常に解析された場合、その型を明示的に解決するようにしました。
- 変更前:
-
func (p *parser) parseVarList(isParam bool) (list []ast.Expr, typ ast.Expr)
- 変更前:
// if we had a list of identifiers, it must be followed by a type if typ = p.tryVarType(isParam); typ != nil { p.resolve(typ) // <-- この行が削除された }
- 変更後:
// if we had a list of identifiers, it must be followed by a type // If any of the results are identifiers, they are not resolved. // <-- 新しいコメント typ = p.tryVarType(isParam) // <-- p.resolve(typ)が削除された
- 変更の意図:
parseVarList
自身が型を解決するのではなく、その結果を受け取る呼び出し元に解決を委譲するようになりました。新しいコメントは、この関数の役割を明確にしています。
- 変更前:
-
func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params []*ast.Field)
- 変更前:
if typ != nil { // IdentifierList Type idents := p.makeIdentList(list) field := &ast.Field{Names: idents, Type: typ} p.declare(field, nil, scope, ast.Var, idents...) // p.resolve(typ) がなかった if p.tok == token.COMMA { p.next() } // ... for p.tok != token.RPAREN && p.tok != token.EOF { idents := p.parseIdentList() typ := p.parseVarType(ellipsisOk) field := &ast.Field{Names: idents, Type: typ} p.declare(field, nil, scope, ast.Var, idents...) // p.resolve(typ) がなかった if !p.atComma("parameter list") { break } p.next() } } else { // Type { "," Type } (anonymous parameters) params = make([]*ast.Field, len(list)) for i, x := range list { p.resolve(x) // <-- xをtypにリネーム params[i] = &ast.Field{Type: x} // <-- xをtypにリネーム } }
- 変更後:
// ParameterDecl // <-- 新しいコメント list, typ := p.parseVarList(ellipsisOk) // analyze case // <-- 新しいコメント if typ != nil { // IdentifierList Type idents := p.makeIdentList(list) field := &ast.Field{Names: idents, Type: typ} p.declare(field, nil, scope, ast.Var, idents...) p.resolve(typ) // <-- 追加された if p.tok == token.COMMA { p.next() } // ... for p.tok != token.RPAREN && p.tok != token.EOF { idents := p.parseIdentList() typ := p.parseVarType(ellipsisOk) field := &ast.Field{Names: idents, Type: typ} p.declare(field, nil, scope, ast.Var, idents...) p.resolve(typ) // <-- 追加された if !p.atComma("parameter list") { break } p.next() } } else { // Type { "," Type } (anonymous parameters) params = make([]*ast.Field, len(list)) for i, typ := range list { // <-- xをtypにリネーム p.resolve(typ) // <-- xをtypにリネーム params[i] = &ast.Field{Type: typ} // <-- xをtypにリネーム } }
- 変更の意図: 関数パラメータの型が、識別子リストの後に続く場合(例:
a, b int
)と、個別のパラメータ宣言の場合(例:a int, b string
)の両方で、p.declare
の直後にp.resolve(typ)
が呼び出されるようになりました。これにより、すべてのパラメータ型が確実に解決されます。匿名パラメータのループ変数もx
からtyp
にリネームされ、可読性が向上しています。
- 変更前:
src/pkg/go/parser/parser_test.go
func TestUnresolved(t *testing.T)
の追加- この新しいテスト関数は、様々な関数シグネチャと構造体フィールド宣言を含むGoのソースコードを解析し、パーサーが
f.Unresolved
リストに格納する「未解決の識別子」を収集します。 want
変数には、Goの組み込み型(int
,byte
,float
,complex
など)のように、パーサー自身が明示的に解決する必要がない(コンパイラの型チェッカーが最終的に解決する)識別子のリストが期待値として定義されています。- このテストの目的は、パーサーが「解決すべき型」を適切に解決し、「解決する必要がない(または後続フェーズで解決される)型」を未解決リストに正しく残すことを検証することです。このテストの追加は、今回の変更がパーサーの型解決ロジックに与える影響を正確に捉えるために不可欠です。
- この新しいテスト関数は、様々な関数シグネチャと構造体フィールド宣言を含むGoのソースコードを解析し、パーサーが
コアとなるコードの解説
このコミットのコアとなる変更は、p.resolve(typ)
メソッドの戦略的な配置にあります。
p.resolve(typ)
は、go/parser
パッケージの内部で、ASTノードtyp
が表す型を、その識別子が参照する実際の型定義にリンクさせる役割を担っています。例えば、ソースコードにvar x MyType
という宣言があった場合、パーサーはMyType
という識別子を解析し、それをASTノードとして表現します。p.resolve(MyTypeASTNode)
が呼び出されると、パーサーは現在のスコープ内でMyType
という名前の型定義を探し、見つかればその型定義への参照をMyTypeASTNode
に設定します。これにより、ASTは単なる構文構造だけでなく、セマンティックな情報(この識別子がどの定義を参照しているか)も含むようになります。
変更前は、特に以下のようなケースでp.resolve(typ)
の呼び出しが漏れていました。
- 匿名フィールド: 構造体内の匿名フィールド(例:
struct { io.Reader }
)の型。 - 可変長パラメータ:
func f(args ...int)
のような可変長パラメータの型。 - 複数のパラメータ宣言:
func f(a, b int)
のように、複数のパラメータが同じ型を持つ場合の型。
これらのケースでp.resolve(typ)
が適切に呼び出されないと、生成されるASTではこれらの型が「未解決」のままとなり、後続のコンパイルフェーズで問題を引き起こす可能性がありました。
今回の変更では、これらの見落としを修正し、parseFieldDecl
、tryVarType
、parseParameterList
といった関数内で、型が特定された直後、またはフィールド/パラメータがスコープに宣言された直後にp.resolve(typ)
を呼び出すようにしました。
特にparseVarList
からのp.resolve(typ)
の削除は重要です。これは、parseVarList
が識別子のリストとそれに続く型を解析する汎用的な関数であり、その型が常に即座に解決されるべきではないという設計上の判断を示しています。例えば、var a, b int
のような変数宣言の場合、int
型はparseVarList
で解析されますが、その解決は変数宣言全体のコンテキストで行われるべきです。そのため、parseVarList
は型を「見つける」だけで、その解決は呼び出し元に委ねるという役割分担が明確化されました。
新しいTestUnresolved
テストは、この変更が意図通りに機能していることを検証します。このテストは、パーサーがASTを構築した後に残る「未解決の識別子」のリストをチェックします。期待される未解決の識別子は、int
, byte
, float
などの組み込み型です。これらの型はGoの言語仕様で定義されており、パーサーが明示的に解決しなくても、コンパイラの型チェッカーが最終的に解決できるため、未解決のままで問題ありません。このテストがパスすることで、パーサーが「解決すべき型」を適切に解決し、「未解決のままで良い型」を正しく識別していることが確認されます。
関連リンク
- Go Issue #3655: https://github.com/golang/go/issues/3655
- Gerrit Change-Id (CL) 6213065: https://golang.org/cl/6213065
参考にした情報源リンク
- Go言語の公式ドキュメント (go.dev)
- Go言語のソースコード (github.com/golang/go)
- Go言語の
go/parser
およびgo/ast
パッケージのドキュメント - コンパイラ設計に関する一般的な情報源(構文解析、AST、シンボル解決など)