[インデックス 15409] ファイルの概要
このコミットは、Go言語の型チェッカー(go/types
パッケージ)における、複合リテラル(composite literal)のキー解決に関するバグ修正です。具体的には、複合リテラルのキーが未解決の識別子である場合に、現在のスコープだけでなく、Go言語の組み込み型や定数が定義されているUniverse
スコープも参照するように変更されています。これにより、true
やfalse
のような組み込みの識別子をマップのキーとして使用する際に、正しく型解決が行われるようになります。
コミット
commit 670f6b602d1e9ef6d1ce54830593415f62d48246
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Sun Feb 24 21:57:16 2013 -0800
go/types: unresolved literal keys must be looked up in universe.
Fixes #4888.
R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/7383051
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/670f6b602d1e9ef6d1ce54830593415f62d48246
元コミット内容
go/types: unresolved literal keys must be looked up in universe.
Fixes #4888.
R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/7383051
変更の背景
この変更は、Go言語のIssue #4888「go/types
: map literal key true
is undeclared」を修正するために行われました。このIssueは、マップリテラル(map[bool]int{true: 1}
のような構文)において、キーとしてtrue
やfalse
といった組み込みのブール値リテラルを使用すると、型チェッカーがこれらを未宣言の識別子として誤って報告するというバグを指摘していました。
Go言語の型チェッカーは、複合リテラルのキーを解決する際に、まず現在のパッケージスコープ内で識別子を検索します。しかし、true
やfalse
のような組み込みの定数は、特定のパッケージスコープではなく、Go言語の「Universeスコープ」と呼ばれるグローバルなスコープに存在します。このバグは、型チェッカーがパッケージスコープでの検索に失敗した後、Universeスコープを適切に参照していなかったために発生していました。
この修正により、型チェッカーは未解決の複合リテラルキーをUniverseスコープでも検索するようになり、true
やfalse
などの組み込み識別子が正しく解決され、マップリテラルで問題なく使用できるようになりました。
前提知識の解説
Go言語の型システムと型チェッカー
Go言語は静的型付け言語であり、プログラムの実行前に型チェックが行われます。型チェッカーは、コードがGo言語の型規則に準拠しているかを確認し、型に関するエラーを検出します。go/types
パッケージは、Goコンパイラの一部として、この型チェックのロジックを提供します。
スコープ (Scope)
プログラミング言語におけるスコープとは、識別子(変数名、関数名、型名など)が参照可能であるプログラムの領域を指します。Go言語にはいくつかのスコープがあります。
- Universeスコープ: Go言語のすべてのプログラムで常に利用可能な組み込みの識別子(
int
,string
,true
,false
,nil
,make
,len
など)が定義されている最も外側のスコープです。 - パッケージスコープ: パッケージレベルで宣言された識別子(グローバル変数、関数、型など)が利用可能なスコープです。
- ファイルスコープ: パッケージ内の特定のファイルでのみ利用可能な識別子(通常はパッケージスコープに含まれる)。
- ブロックスコープ:
if
文、for
文、関数本体などのブロック内で宣言された識別子が利用可能なスコープです。
識別子を解決する際、Goのコンパイラ(型チェッカー)は、最も内側のスコープから順に外側のスコープへと検索を進めます。
複合リテラル (Composite Literals)
複合リテラルは、構造体、配列、スライス、マップなどの複合型の値を構築するための構文です。
- 構造体リテラル:
Point{X: 10, Y: 20}
- 配列/スライスリテラル:
[]int{1, 2, 3}
- マップリテラル:
map[string]int{"key": 1, "another": 2}
マップリテラルの場合、key: value
の形式で要素を指定します。このkey
の部分が識別子である場合、型チェッカーはその識別子を解決する必要があります。
ast.Ident
とast.Expr
Go言語のコンパイラは、ソースコードを抽象構文木(AST: Abstract Syntax Tree)にパースします。
ast.Expr
: 任意の式を表すインターフェースです。複合リテラルのキーも式として扱われます。ast.Ident
: 識別子(変数名、関数名など)を表すASTノードです。
型チェッカーは、ast.Expr
がast.Ident
であるかどうかをチェックし、その識別子を解決しようとします。
技術的詳細
このコミットの変更は、src/pkg/go/types/expr.go
ファイルのcompositeLitKey
関数にあります。この関数は、複合リテラルのキーを処理する役割を担っています。
変更前のコードでは、複合リテラルのキーが識別子(ast.Ident
)であり、かつその識別子がまだ解決されていない(ident.Obj == nil
)場合に、まず現在のパッケージスコープ(check.pkg.Scope
)内でその識別子を検索していました。
// 変更前
if ident, ok := key.(*ast.Ident); ok && ident.Obj == nil {
if obj := check.pkg.Scope.Lookup(ident.Name); obj != nil {
check.register(ident, obj)
} else {
check.errorf(ident.Pos(), "undeclared name: %s", ident.Name)
}
}
このロジックでは、true
やfalse
のようなUniverseスコープに存在する識別子が、現在のパッケージスコープには存在しないため、check.pkg.Scope.Lookup
がnil
を返し、結果として「undeclared name」(未宣言の名前)エラーが発生していました。
このコミットでは、パッケージスコープでの検索に失敗した場合に、さらにUniverse
スコープを検索するロジックが追加されました。
// 変更後
if ident, ok := key.(*ast.Ident); ok && ident.Obj == nil {
if obj := check.pkg.Scope.Lookup(ident.Name); obj != nil {
check.register(ident, obj)
} else if obj := Universe.Lookup(ident.Name); obj != nil { // ここが追加された行
check.register(ident, obj)
} else {
check.errorf(ident.Pos(), "undeclared name: %s", ident.Name)
}
}
Universe.Lookup(ident.Name)
が追加されたことで、型チェッカーはtrue
やfalse
といったUniverseスコープの識別子を正しく見つけ出し、それらを複合リテラルのキーとして使用できるようになりました。check.register(ident, obj)
は、見つかったオブジェクト(obj
)を識別子(ident
)に関連付けることで、その識別子が解決済みであることを型チェッカーに登録します。
また、テストファイルsrc/pkg/go/types/testdata/expr3.src
には、この修正によって正しく動作するようになるマップリテラルのテストケースが追加されています。
_ = M1{true: 1, false: 0} // map[bool]int のテスト
_ = M2{nil: 0, &index2: 1} // map[*int]int のテスト (nilもUniverseスコープの識別子)
これらのテストケースは、true
、false
、nil
といったUniverseスコープの識別子がマップのキーとして正しく扱われることを検証しています。
コアとなるコードの変更箇所
diff --git a/src/pkg/go/types/expr.go b/src/pkg/go/types/expr.go
index e7ea2843a0..9d43a887bf 100644
--- a/src/pkg/go/types/expr.go
+++ b/src/pkg/go/types/expr.go
@@ -543,6 +543,8 @@ func (check *checker) compositeLitKey(key ast.Expr) {
if ident, ok := key.(*ast.Ident); ok && ident.Obj == nil {
if obj := check.pkg.Scope.Lookup(ident.Name); obj != nil {
check.register(ident, obj)
+ } else if obj := Universe.Lookup(ident.Name); obj != nil {
+ check.register(ident, obj)
} else {
check.errorf(ident.Pos(), "undeclared name: %s", ident.Name)
}
diff --git a/src/pkg/go/types/testdata/expr3.src b/src/pkg/go/types/testdata/expr3.src
index 519e3f567a..9dc95b4af3 100644
--- a/src/pkg/go/types/testdata/expr3.src
+++ b/src/pkg/go/types/testdata/expr3.src
@@ -259,6 +259,8 @@ var index2 int = 2
func map_literals() {
type M0 map[string]int
+ type M1 map[bool]int
+ type M2 map[*int]int
_ = M0{}
_ = M0{1 /* ERROR "missing key" */ }
@@ -267,11 +269,14 @@ func map_literals() {
_ = M0{"foo": 1, "bar": 2, "foo" /* ERROR "duplicate key" */ : 3 }
// map keys must be resolved correctly
- // (for detials, see comment in go/parser/parser.go, method parseElement)
+ // (for details, see comment in go/parser/parser.go, method parseElement)
key1 := "foo"
_ = M0{key1: 1}
_ = M0{key2: 2}
_ = M0{key3 /* ERROR "undeclared name" */ : 2}
+
+ _ = M1{true: 1, false: 0}
+ _ = M2{nil: 0, &index2: 1}
}
var key2 string = "bar"
@@ -364,4 +369,4 @@ func _calls() {
fi(g2())
fi(0, g2)
fi(0, g2 /* ERROR "2-valued expression" */ ())
-}
\ No newline at end of file
+}\ No newline at end of file
コアとなるコードの解説
src/pkg/go/types/expr.go
このファイルは、Go言語の型チェッカーにおける式(expression)の型付けと解決に関するロジックを含んでいます。
func (check *checker) compositeLitKey(key ast.Expr)
:- この関数は、複合リテラル(例:
map[string]int{"key": 1}
の"key"
部分)のキーを型チェックし、解決するために呼び出されます。 if ident, ok := key.(*ast.Ident); ok && ident.Obj == nil
:key
がast.Ident
(識別子)型にキャスト可能であり、かつその識別子にまだオブジェクト(Obj
)が関連付けられていない(つまり、まだ解決されていない)場合に、このブロックに入ります。
if obj := check.pkg.Scope.Lookup(ident.Name); obj != nil
:- まず、現在のパッケージスコープ(
check.pkg.Scope
)内でident.Name
(識別子の名前)を検索します。 - もし見つかれば(
obj != nil
)、check.register(ident, obj)
を呼び出して、この識別子を解決済みとして登録します。
- まず、現在のパッケージスコープ(
else if obj := Universe.Lookup(ident.Name); obj != nil
:- この行が追加された部分です。
- もしパッケージスコープで識別子が見つからなかった場合(
else
)、次にUniverse
スコープ内で同じ名前を検索します。 Universe
スコープには、true
,false
,nil
などの組み込みの識別子が含まれています。- もし
Universe
スコープで見つかれば、同様にcheck.register(ident, obj)
で登録します。
else
:- パッケージスコープでも
Universe
スコープでも識別子が見つからなかった場合、check.errorf(...)
を呼び出して「undeclared name」(未宣言の名前)エラーを報告します。
- パッケージスコープでも
- この関数は、複合リテラル(例:
この変更により、型チェッカーは複合リテラルのキーとして使用される識別子を、より広範なスコープ(Universeスコープ)で検索するようになり、true
やfalse
のような組み込みの識別子が正しく認識されるようになりました。
src/pkg/go/types/testdata/expr3.src
このファイルは、go/types
パッケージの型チェッカーのテストデータとして使用されるGoのソースコードスニペットです。このコミットでは、マップリテラルに関する新しいテストケースが追加されています。
type M1 map[bool]int
とtype M2 map[*int]int
の新しいマップ型が定義されました。_ = M1{true: 1, false: 0}
:map[bool]int
型のマップリテラルで、キーとしてtrue
とfalse
(Universeスコープの組み込み定数)を使用しています。この行は、修正前はエラーになっていましたが、修正後は正しく型チェックされるようになりました。
_ = M2{nil: 0, &index2: 1}
:map[*int]int
型のマップリテラルで、キーとしてnil
(Universeスコープの組み込み識別子)とポインタを使用しています。nil
も同様に、修正前はエラーになる可能性がありましたが、修正後は正しく型チェックされるようになりました。
// (for details, see comment in go/parser/parser.go, method parseElement)
:- コメントのスペルミス「detials」が「details」に修正されています。これはコードの機能には影響しませんが、ドキュメントの品質向上に貢献しています。
これらのテストケースの追加は、このコミットが解決しようとしている問題(Universeスコープの識別子が複合リテラルのキーとして認識されない問題)が実際に修正されたことを検証するために重要です。
関連リンク
- Go Issue #4888:
go/types
: map literal keytrue
is undeclared - https://github.com/golang/go/issues/4888 - Go CL 7383051:
go/types
: unresolved literal keys must be looked up in universe. - https://golang.org/cl/7383051 (Gerrit Code Review)
参考にした情報源リンク
- Go言語の公式ドキュメント(
go/types
パッケージ、スコープ、複合リテラルに関する情報) - Go言語のIssueトラッカー(GitHub)
- Go言語のGerrit Code Reviewシステム
- Go言語のASTに関する一般的な情報
- Go言語の
Universe
スコープに関する情報