Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15409] ファイルの概要

このコミットは、Go言語の型チェッカー(go/typesパッケージ)における、複合リテラル(composite literal)のキー解決に関するバグ修正です。具体的には、複合リテラルのキーが未解決の識別子である場合に、現在のスコープだけでなく、Go言語の組み込み型や定数が定義されているUniverseスコープも参照するように変更されています。これにより、truefalseのような組み込みの識別子をマップのキーとして使用する際に、正しく型解決が行われるようになります。

コミット

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}のような構文)において、キーとしてtruefalseといった組み込みのブール値リテラルを使用すると、型チェッカーがこれらを未宣言の識別子として誤って報告するというバグを指摘していました。

Go言語の型チェッカーは、複合リテラルのキーを解決する際に、まず現在のパッケージスコープ内で識別子を検索します。しかし、truefalseのような組み込みの定数は、特定のパッケージスコープではなく、Go言語の「Universeスコープ」と呼ばれるグローバルなスコープに存在します。このバグは、型チェッカーがパッケージスコープでの検索に失敗した後、Universeスコープを適切に参照していなかったために発生していました。

この修正により、型チェッカーは未解決の複合リテラルキーをUniverseスコープでも検索するようになり、truefalseなどの組み込み識別子が正しく解決され、マップリテラルで問題なく使用できるようになりました。

前提知識の解説

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.Identast.Expr

Go言語のコンパイラは、ソースコードを抽象構文木(AST: Abstract Syntax Tree)にパースします。

  • ast.Expr: 任意の式を表すインターフェースです。複合リテラルのキーも式として扱われます。
  • ast.Ident: 識別子(変数名、関数名など)を表すASTノードです。

型チェッカーは、ast.Exprast.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)
    }
}

このロジックでは、truefalseのようなUniverseスコープに存在する識別子が、現在のパッケージスコープには存在しないため、check.pkg.Scope.Lookupnilを返し、結果として「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)が追加されたことで、型チェッカーはtruefalseといった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スコープの識別子)

これらのテストケースは、truefalsenilといった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:
      • keyast.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スコープ)で検索するようになり、truefalseのような組み込みの識別子が正しく認識されるようになりました。

src/pkg/go/types/testdata/expr3.src

このファイルは、go/typesパッケージの型チェッカーのテストデータとして使用されるGoのソースコードスニペットです。このコミットでは、マップリテラルに関する新しいテストケースが追加されています。

  • type M1 map[bool]inttype M2 map[*int]intの新しいマップ型が定義されました。
  • _ = M1{true: 1, false: 0}:
    • map[bool]int型のマップリテラルで、キーとしてtruefalse(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言語の公式ドキュメント(go/typesパッケージ、スコープ、複合リテラルに関する情報)
  • Go言語のIssueトラッカー(GitHub)
  • Go言語のGerrit Code Reviewシステム
  • Go言語のASTに関する一般的な情報
  • Go言語のUniverseスコープに関する情報