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

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

このコミットは、Go言語のコンパイラ(cmd/gc)における「理想的なブール型(ideal bool)」の扱いを、最新の言語仕様に合わせて更新するものです。具体的には、ブールリテラルや比較演算の結果として得られる型のないブール値が、どのように型付けされるかに関するルールが修正されています。これにより、型推論の挙動がより厳密になり、Go言語の型システムの一貫性が向上します。

コミット

  • コミットハッシュ: 4ad505d1028bce57be165c343685edbc2518b4fc
  • 作者: Russ Cox rsc@golang.org
  • コミット日時: 2013年2月3日(日)01:44:03 -0500

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/4ad505d1028bce57be165c343685edbc2518b4fc

元コミット内容

    cmd/gc: update ideal bool rules to match latest spec
    
    Fixes #3915.
    Fixes #3923.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/7281044

変更の背景

このコミットは、Go言語のコンパイラが抱えていた2つのバグ(Issue 3915とIssue 3923)を修正するために行われました。これらの問題は、Go言語の型システムにおける「型なしのブール値(untyped boolean)」、特に比較演算の結果やブールリテラルが、どのように特定のブール型(bool型やユーザー定義のブール型)に変換されるか、あるいは型推論されるかに関する挙動の不整合に起因していました。

  • Issue 3915: mybool = x < ymybool = bool(x < y) と解釈されるべき この問題は、ユーザー定義のブール型(例: type mybool bool)に、型なしのブール値(例: x < y の結果)を代入する際に発生しました。Go言語の仕様では、型なしの定数は、代入先の型に変換可能であればその型を持つべきです。しかし、コンパイラは x < y の結果を直接 mybool 型に変換するのではなく、一度組み込みの bool 型に変換してから mybool 型に代入しようとしていました。これにより、myboolbool の間で型が一致しないというエラーが発生することがありました。

  • Issue 3923: c1 == c2 のような比較で型なしブールが型付きブールに変換されるべきではない この問題は、異なるユーザー定義のブール型を持つ変数同士の比較(例: c1 == c2)において、コンパイラが型なしのブール値を不適切に型付きのブール値に変換しようとすることで発生しました。Go言語の仕様では、比較演算子(==, !=)は、オペランドが同じ型であるか、あるいは比較可能である場合にのみ適用できます。型なしのブール値は、特定の型を持つブール値とは異なる扱いを受けるべきであり、不適切な型変換はコンパイルエラーを引き起こす原因となっていました。

これらの問題は、Go言語の型システム、特に型なしの定数と型付きの変数間の相互作用に関するコンパイラの解釈が、言語仕様と完全に一致していなかったことを示しています。このコミットは、これらの不整合を解消し、コンパイラの挙動を言語仕様に厳密に合わせることを目的としています。

前提知識の解説

このコミットを理解するためには、Go言語の型システム、特に以下の概念について理解しておく必要があります。

  1. 型(Types): Go言語は静的型付け言語であり、すべての変数には型があります。型は、その変数が保持できる値の種類と、その値に対して実行できる操作を定義します。

    • 組み込み型(Built-in Types): int, float64, string, bool など、言語に組み込まれている基本的な型です。
    • ユーザー定義型(User-defined Types): type MyInt int のように、既存の型を基にして新しい型を定義できます。ユーザー定義型は、基となる型と同じ基底型を持ちますが、異なる型として扱われます。例えば、MyIntint は異なる型であり、明示的な型変換なしには代入できません。
  2. 定数(Constants): Go言語の定数は、コンパイル時に値が決定される不変のエンティティです。定数には「型付き(typed)」と「型なし(untyped)」の2種類があります。

    • 型付き定数(Typed Constants): const x int = 10 のように、明示的に型が指定された定数です。
    • 型なし定数(Untyped Constants): const x = 10 のように、型が明示的に指定されていない定数です。型なし定数は、その値が使用される文脈(代入先、演算のオペランドなど)に応じて、適切な型に「昇格(promoted)」または「変換(converted)」されます。型なし定数は、Go言語の柔軟な型推論を可能にする重要な機能です。
  3. 理想的な型(Ideal Types)/ 型なしの定数(Untyped Constants): Go言語の仕様では、数値リテラル(例: 10, 3.14)、ブールリテラル(true, false)、文字列リテラル("hello")は、デフォルトでは「型なし」の定数として扱われます。これらの型なし定数は、特定の型に束縛されることなく、より広い範囲の値を表現できます。

    • 型なしのブール値(Untyped Boolean): 比較演算子(<, >, ==, != など)の結果は、常に型なしのブール値となります。例えば、x < y の結果は true または false ですが、これは bool 型ではなく、型なしのブール値として扱われます。
    • 型なし定数の型推論と変換: 型なし定数は、代入や演算の文脈で型が決定されます。
      • 変数への代入: var b bool = x < y の場合、型なしのブール値 x < ybool 型に変換されます。
      • ユーザー定義型への代入: type MyBool bool; var mb MyBool = x < y の場合、型なしのブール値 x < yMyBool 型に変換されます。
      • 演算: true && false のように、型なしのブール値同士の演算結果も型なしのブール値になります。
  4. 型変換(Type Conversions): Go言語では、異なる型間の代入や演算を行う場合、明示的な型変換が必要となることがあります。ただし、型なし定数の場合は、文脈に応じた暗黙的な変換が行われます。

  5. コンパイラの役割: Goコンパイラ(cmd/gc)は、ソースコードを解析し、言語仕様に従って型チェックとコード生成を行います。型なし定数の型推論と変換のルールは、コンパイラが厳密に実装すべき重要な部分です。

このコミットは、特に「型なしのブール値」が、組み込みの bool 型やユーザー定義のブール型にどのように変換されるか、そして異なるブール型間の比較がどのように扱われるかという点に焦点を当てています。

技術的詳細

このコミットの技術的な核心は、Goコンパイラのsrc/cmd/gc/const.cファイルにおけるconvlit1関数の変更にあります。convlit1関数は、リテラル(定数)の型変換を処理する役割を担っています。

変更前は、n->type == idealbool(型なしのブール値)の場合、無条件にn->type = types[TBOOL];として、組み込みのbool型に変換していました。これは、型なしのブール値が常に組み込みのbool型に「昇格」されるという単純なルールを適用していました。

しかし、Go言語の仕様では、型なしの定数は、代入先の型に変換可能であれば、その代入先の型を持つべきです。特に、ユーザー定義のブール型(例: type mybool bool)に型なしのブール値を代入する場合、その型なしのブール値は直接mybool型に変換されるべきです。

このコミットでは、この仕様の解釈を修正しています。変更後のコードは以下のようになります。

		if(n->type == idealbool) {
			if(t->etype == TBOOL)
				n->type = t;
			else
				n->type = types[TBOOL];
		}

この変更のポイントは、if(t->etype == TBOOL)という条件分岐が追加されたことです。

  • tは、変換先の型(ターゲット型)を表します。
  • t->etype == TBOOLは、ターゲット型が組み込みのbool型であるか、またはboolを基底型とするユーザー定義型であるかをチェックします。

変更の挙動:

  1. n->type == idealbool: 現在処理しているノードnが「型なしのブール値」である場合。
  2. if(t->etype == TBOOL): 変換先の型tが、組み込みのbool型、またはboolを基底型とするユーザー定義型(例: mybool)であるかをチェックします。
    • n->type = t;: もしターゲット型がbool型またはその派生型であれば、型なしのブール値nの型を、そのターゲット型tに直接設定します。これにより、mybool = x < y のような代入において、x < y の結果が直接mybool型に変換されるようになります。これはIssue 3915の修正に対応します。
    • else n->type = types[TBOOL];: ターゲット型がbool型またはその派生型でない場合(例えば、型なしのブール値が他の文脈で使用され、特定の型への変換が不要な場合や、デフォルトの型が必要な場合)、型なしのブール値nの型を組み込みのbool型に設定します。これは、一般的な型なしブール値のデフォルトの型推論の挙動を維持します。

この修正により、コンパイラは型なしのブール値を扱う際に、より厳密にGo言語の型変換ルールに従うようになります。特に、ユーザー定義のブール型への代入や、異なるブール型間の比較において、型の一貫性が保たれるようになります。

test/const6.goは、この変更が正しく機能することを確認するための新しいテストファイルです。このテストファイルは、様々なシナリオで型なしのブール値と型付きのブール値の相互作用を検証し、期待されるコンパイルエラー(ERROR "mismatched types")が発生することを確認しています。これにより、Issue 3923で指摘されたような、不適切な型変換によるエラーが正しく検出されるようになります。

コアとなるコードの変更箇所

src/cmd/gc/const.cconvlit1 関数内の変更:

--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -78,7 +78,6 @@ convlit1(Node **np, Type *t, int explicit)\n \tif(!explicit && !isideal(n->type))\n \t\treturn;\n \n-\t\n \tif(n->op == OLITERAL) {\n \t\tnn = nod(OXXX, N, N);\n \t\t*nn = *n;\n@@ -88,8 +87,12 @@ convlit1(Node **np, Type *t, int explicit)\n \n \tswitch(n->op) {\n \tdefault:\n-\t\tif(n->type == idealbool)\n-\t\t\tn->type = types[TBOOL];\n+\t\tif(n->type == idealbool) {\n+\t\t\t\tif(t->etype == TBOOL)\n+\t\t\t\t\tn->type = t;\n+\t\t\t\telse\n+\t\t\t\t\tn->type = types[TBOOL];\n+\t\t}\n \t\tif(n->type->etype == TIDEAL) {\n \t\t\tconvlit(&n->left, t);\n \t\t\tconvlit(&n->right, t);\

test/const6.go の追加:

--- /dev/null
+++ b/test/const6.go
@@ -0,0 +1,30 @@
+// errorcheck
+
+// Copyright 2013 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Ideal vs non-ideal bool. See issue 3915, 3923.
+
+package p
+
+type mybool bool
+type mybool1 bool
+
+var (
+	x, y int = 1, 2
+	c1 bool = x < y
+	c2 mybool = x < y
+	c3 mybool = c2 == (x < y)
+	c4 mybool = c2 == (1 < 2)
+	c5 mybool = 1 < 2
+	c6 mybool1 = x < y
+	c7 = c1 == c2 // ERROR "mismatched types"
+	c8 = c2 == c6 // ERROR "mismatched types"
+	c9 = c1 == c6 // ERROR "mismatched types"
+	_ = c2 && (x < y)
+	_ = c2 && (1 < 2)
+	_ = c1 && c2 // ERROR "mismatched types"
+	_ = c2 && c6 // ERROR "mismatched types"
+	_ = c1 && c6 // ERROR "mismatched types"
+)

コアとなるコードの解説

src/cmd/gc/const.c の変更

この変更は、Goコンパイラの定数処理部分、特に型なしのブール値(idealbool)の型推論ロジックを修正しています。

変更前のコード:

		if(n->type == idealbool)
			n->type = types[TBOOL];

これは、型なしのブール値が常に組み込みのbool型に変換されることを意味していました。

変更後のコード:

		if(n->type == idealbool) {
			if(t->etype == TBOOL)
				n->type = t;
			else
				n->type = types[TBOOL];
		}

この修正により、型なしのブール値の型推論がより洗練されました。

  • n->type == idealbool: 処理中のノードnが型なしのブール値である場合。
  • if(t->etype == TBOOL): 変換先の型tが、組み込みのbool型であるか、またはboolを基底型とするユーザー定義型(例: type mybool bool)であるかをチェックします。
    • n->type = t;: もし変換先の型がbool型またはその派生型であれば、型なしのブール値の型を直接そのターゲット型tに設定します。これにより、mybool = x < y のような代入で、x < y の結果が直接mybool型に変換されるようになります。これはIssue 3915の修正です。
    • else n->type = types[TBOOL];: 変換先の型がbool型またはその派生型でない場合(例えば、型なしのブール値が他の文脈で使用され、特定の型への変換が不要な場合や、デフォルトの型が必要な場合)、型なしのブール値の型を組み込みのbool型に設定します。これは、一般的な型なしブール値のデフォルトの型推論の挙動を維持します。

この変更は、Go言語の型システムにおける「型なしの定数」の挙動を、言語仕様に厳密に合わせるための重要な修正です。

test/const6.go の追加

このテストファイルは、上記のconst.cの変更が正しく機能することを確認するために追加されました。特に、型なしのブール値とユーザー定義のブール型との間の相互作用に焦点を当てています。

package p

type mybool bool
type mybool1 bool

var (
	x, y int = 1, 2
	c1 bool = x < y      // c1: bool型に型推論される (x < y は型なしブール)
	c2 mybool = x < y    // c2: mybool型に型推論される (x < y は型なしブール)
	c3 mybool = c2 == (x < y) // c3: mybool型に型推論される (c2はmybool, x < yは型なしブールだが、比較結果はmyboolに変換可能)
	c4 mybool = c2 == (1 < 2) // c4: mybool型に型推論される (c2はmybool, 1 < 2は型なしブールだが、比較結果はmyboolに変換可能)
	c5 mybool = 1 < 2    // c5: mybool型に型推論される (1 < 2 は型なしブール)
	c6 mybool1 = x < y   // c6: mybool1型に型推論される (x < y は型なしブール)

	// 以下の行はコンパイルエラーになることを期待する
	c7 = c1 == c2 // ERROR "mismatched types" (bool と mybool は異なる型)
	c8 = c2 == c6 // ERROR "mismatched types" (mybool と mybool1 は異なる型)
	c9 = c1 == c6 // ERROR "mismatched types" (bool と mybool1 は異なる型)

	_ = c2 && (x < y) // OK: c2はmybool, x < yは型なしブールだが、論理AND演算は可能
	_ = c2 && (1 < 2) // OK: c2はmybool, 1 < 2は型なしブールだが、論理AND演算は可能

	// 以下の行はコンパイルエラーになることを期待する
	_ = c1 && c2 // ERROR "mismatched types" (bool と mybool は異なる型)
	_ = c2 && c6 // ERROR "mismatched types" (mybool と mybool1 は異なる型)
	_ = c1 && c6 // ERROR "mismatched types" (bool と mybool1 は異なる型)
)

このテストの重要な点は、c1, c2, c6 のように、型なしのブール値が代入先の型(bool, mybool, mybool1)に正しく型推論されることを確認している点です。

また、c7, c8, c9 や、論理AND演算の後のエラー行は、異なるユーザー定義型(bool, mybool, mybool1)の間で比較や論理演算を行おうとすると、Go言語の厳密な型チェックにより「mismatched types」(型不一致)エラーが発生することを示しています。これは、型なしのブール値が正しく型推論された後、それぞれの型が独立して扱われることを保証します。Issue 3923で指摘されたような、不適切な型変換によるエラーが正しく検出されることを検証しています。

関連リンク

参考にした情報源リンク

  • 上記の関連リンクに記載されているGoのIssueトラッカー、Goのコードレビューシステム(CL)、およびGo言語の公式仕様書。
  • Go言語の型システムに関する一般的なドキュメントやチュートリアル(Goの型推論、定数、型変換について解説しているもの)。
  • Goコンパイラのソースコード(src/cmd/gc/const.c)の分析。
  • Goのテストコード(test/const6.go)の分析。
  • Go言語のコンパイラ設計に関する一般的な知識。