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

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

このコミットは、Go言語の型チェッカー (go/types パッケージ) において、定数式での string(x) 型変換の挙動を実装したものです。特に、整数定数を文字列に変換する際の処理が改善され、コンパイル時にその結果が確定できるようになりました。これにより、Go言語のコンパイラがより多くの定数式を正しく評価できるようになり、コンパイル時のエラーチェックと最適化の精度が向上します。

コミット

commit 0b2caf27177e47b587df6ef4dce2df67ad3e4666
Author: Robert Griesemer <gri@golang.org>
Date:   Wed Mar 6 16:15:04 2013 -0800

    go/types: implement constant string(x) conversions
    
    Fixes #4982.
    
    R=adonovan, r
    CC=golang-dev
    https://golang.org/cl/7537043

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

https://github.com/golang/go/commit/0b2caf27177e47b587df6ef4dce2df67ad3e4666

元コミット内容

go/types: implement constant string(x) conversions

このコミットは、Go言語の型システム (go/types パッケージ) において、定数式での string(x) 型変換を実装するものです。特に、整数 xstring(x) で文字列に変換する際の定数評価を可能にすることで、Issue #4982 を修正します。

変更の背景

Go言語では、コンパイル時に値が確定する「定数」という概念があります。定数同士の演算や型変換も、可能であればコンパイル時に評価され、その結果も定数として扱われます。これにより、実行時のオーバーヘッドを減らし、コンパイル時にエラーを検出できるという利点があります。

しかし、このコミット以前は、string(x) のような特定の型変換が定数式として適切に処理されていませんでした。特に、整数値をUnicodeコードポイントとして解釈し、対応する文字列(UTF-8エンコードされた文字)に変換する string(int) のような操作は、コンパイル時に評価されるべき定数式であるにもかかわらず、その処理が不完全でした。

コミットメッセージにある "Fixes #4982" は、この問題がGo言語のIssueトラッカーで報告されていたことを示唆しています。ユーザーが string(65) のような定数変換を期待通りに扱えない、あるいはコンパイル時にエラーが発生するといった問題があったと考えられます。このコミットは、このような「定数 string(x) 変換」がコンパイル時に正しく評価されるようにすることで、Go言語の型チェッカーの堅牢性と正確性を向上させることを目的としています。

前提知識の解説

Go言語の型システムと go/types パッケージ

Go言語は静的型付け言語であり、すべての変数と式には型があります。go/types パッケージは、Goコンパイラのフロントエンドの一部であり、Goプログラムの型チェックと型推論を担当します。このパッケージは、ソースコードを解析して抽象構文木 (AST) を構築した後、各ノードの型を決定し、型の一貫性を検証します。定数式の評価もこのパッケージの重要な機能の一つです。

定数 (Constants)

Go言語における定数は、コンパイル時に値が確定する不変のエンティティです。数値、真偽値、文字列の定数があります。定数式は、定数と定数演算子のみで構成される式で、その結果も定数となります。例えば、const C = 1 + 2C3 という定数になります。

型変換 (Type Conversions)

Go言語では、異なる型の値を別の型に変換するために型変換構文 T(x) を使用します。ここで T は変換先の型、x は変換元の値です。型変換は、Goの厳格な型システムにおいて、異なる型の間の相互運用性を可能にする重要な機能です。

string(int) 変換

Go言語において、整数値を string 型に変換する string(x) は特別な意味を持ちます。この変換は、整数 x をUnicodeコードポイントとして解釈し、そのコードポイントに対応するUTF-8エンコードされた文字列を生成します。

  • 例えば、string(65) はUnicodeコードポイントU+0041 (大文字の 'A') に対応するため、結果は "A" となります。
  • string(0x30AF) はUnicodeコードポイントU+30AF (カタカナの 'ク') に対応するため、結果は "ク" となります。
  • 無効なUnicodeコードポイント(例えば、負の数やU+10FFFFを超える値)を string(x) で変換しようとすると、Unicodeの「代替文字 (Replacement Character)」である U+FFFD () が返されます。これはUTF-8では \uFFFD と表現されます。

このコミットの目的は、このような string(int) 変換が、x が定数である場合にコンパイル時に評価され、その結果も定数として扱われるようにすることです。

技術的詳細

このコミットの主要な変更は、src/pkg/go/types/conversions.go ファイル内の checker.conversion メソッドにあります。このメソッドは、Goプログラム内の型変換式を処理する役割を担っています。

変更前は、定数変換のセクションに // TODO(gri) implement this というコメントがあり、定数 string(x) 変換が未実装であることが示されていました。このコミットでは、この部分に具体的なロジックが追加されました。

追加されたロジックは以下のステップで動作します。

  1. 定数変換の検出: 変換元のオペランド x が定数 (x.mode == constant) であり、変換先の型 typ も定数型 (isConstType(typ)) である場合に、定数変換のパスに入ります。
  2. string(x) 変換の特定: 変換先の型が string 型であるかをチェックします (typ.Kind == String)。
  3. 整数からの変換:
    • 変換元 x が整数型である場合 (x.isInteger(check.ctxt))、x.val から int64 型の codepoint を抽出します。
    • codepoint の抽出に失敗した場合(例: 値が int64 の範囲を超えている、または不明な場合)、codepoint-1 に設定します。これは、Goの組み込み string() 関数が負の整数に対して \uFFFD を返す挙動を模倣するためです。
    • x.valstring(codepoint) の結果で更新します。これにより、コンパイル時に文字列定数が生成されます。
  4. 文字列からの変換:
    • 変換元 x が既に文字列型である場合 (isString(x.typ))、特別な処理は不要です(string から string への変換は自明)。
  5. その他の型からの変換:
    • 上記以外の型(例: bool, float, nil)から string への定数変換は許可されません。これらのケースでは goto ErrorMsg を使用してエラーパスに分岐し、「cannot convert %s to %s」というエラーメッセージを生成します。
  6. エラー処理の改善: ErrorMsg ラベルが導入され、check.invalidOp を呼び出して適切なエラーメッセージを生成し、オペランドのモードを invalid に設定します。これにより、型変換が不正である場合に、より正確なエラー報告が可能になります。

この変更により、string(65) のような式はコンパイル時に "A" という文字列定数として評価され、string(-1)string(1234567890) のような無効なコードポイントの変換も "\uFFFD" という文字列定数として評価されるようになります。

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

src/pkg/go/types/conversions.go

func (check *checker) conversion(x *operand, conv *ast.CallExpr, typ Type, iota int6) メソッド内の if x.mode == constant && isConstType(typ) ブロックに、定数 string(x) 変換のロジックが追加されました。

--- a/src/pkg/go/types/conversions.go
+++ b/src/pkg/go/types/conversions.go
@@ -30,12 +30,32 @@ func (check *checker) conversion(x *operand, conv *ast.CallExpr, typ Type, iota
 
 	if x.mode == constant && isConstType(typ) {
 		// constant conversion
-		// TODO(gri) implement this
+		typ := underlying(typ).(*Basic)
+		// For now just implement string(x) where x is an integer,
+		// as a temporary work-around for issue 4982, which is a
+		// common issue.
+		if typ.Kind == String {
+			switch {
+			case x.isInteger(check.ctxt):
+				codepoint, ok := x.val.(int64)
+				if !ok {
+					// absolute value too large (or unknown) for conversion;
+					// same as converting any other out-of-range value - let
+					// string(codepoint) do the work
+					codepoint = -1
+				}
+				x.val = string(codepoint)
+			case isString(x.typ):
+				// nothing to do
+			default:
+				goto ErrorMsg
+			}
+		}
+		// TODO(gri) verify the remaining conversions.
 	} else {
 		// non-constant conversion
 		if !x.isConvertible(check.ctxt, typ) {
-\t\t\tcheck.invalidOp(conv.Pos(), "cannot convert %s to %s", x, typ)
-\t\t\tgoto Error
+\t\t\tgoto ErrorMsg
 		}
 		x.mode = value
 	}
@@ -45,8 +65,11 @@ func (check *checker) conversion(x *operand, conv *ast.CallExpr, typ Type, iota
 	x.typ = typ
 	return
 
+ErrorMsg:
+\tcheck.invalidOp(conv.Pos(), "cannot convert %s to %s", x, typ)
 Error:
 	x.mode = invalid
+\tx.expr = conv
 }
 
 func (x *operand) isConvertible(ctxt *Context, T Type) bool {

src/pkg/go/types/operand.go

func (x *operand) isInteger(ctxt *Context) bool メソッドのコメントが更新されました。これは直接的な機能変更ではありませんが、ctxt 引数が不要になる可能性を示唆しています。

--- a/src/pkg/go/types/operand.go
+++ b/src/pkg/go/types/operand.go
@@ -205,6 +205,7 @@ func (x *operand) isAssignable(ctxt *Context, T Type) bool {
 }
 
 // isInteger reports whether x is a (typed or untyped) integer value.
+// TODO(gri) remove ctxt argument - it is not required for UntypedInt.
 func (x *operand) isInteger(ctxt *Context) bool {
 	return x.mode == invalid ||
 	       isInteger(x.typ) ||

src/pkg/go/types/testdata/conversions.src

テストデータが追加され、新しい string_conversions 関数が定義されました。これにより、定数 string(int) 変換の挙動が検証されます。

--- a/src/pkg/go/types/testdata/conversions.src
+++ b/src/pkg/go/types/testdata/conversions.src
@@ -8,11 +8,29 @@ package conversions
 
 // argument count
 var (
-\t_v0 = int /* ERROR "one argument" */ ()\n-\t_v1 = int /* ERROR "one argument" */ (1, 2)\n+\t_ = int /* ERROR "one argument" */ ()\n+\t_ = int /* ERROR "one argument" */ (1, 2)\n )\n 
-//
+func string_conversions() {
+\tconst A = string(65)
+\tassert(A == "A")
+\tconst E = string(-1)
+\tassert(E == "\uFFFD")
+\tassert(E == string(1234567890))
+\n+\ttype myint int
+\tassert(A == string(myint(65)))
+\n+\ttype mystring string
+\tconst _ mystring = mystring("foo")
+\n+\tconst _ = string  /* ERROR "cannot convert" */ (true)
+\tconst _ = string  /* ERROR "cannot convert" */ (1.2)
+\tconst _ = string  /* ERROR "cannot convert" */ (nil)
+}\n+\n+// 
 var (\n-\t_v2 = int8(0)\n+\t_ = int8(0)\n )\n\\ No newline at end of file

コアとなるコードの解説

このコミットの核心は、src/pkg/go/types/conversions.go 内の conversion 関数における定数変換の処理です。

		if typ.Kind == String { // 変換先の型がstringの場合
			switch {
			case x.isInteger(check.ctxt): // 変換元が整数定数の場合
				codepoint, ok := x.val.(int64) // int64としてコードポイントを取得
				if !ok {
					// 値がint64の範囲外の場合、-1を設定して\uFFFDを生成させる
					codepoint = -1
				}
				x.val = string(codepoint) // コンパイル時にstring(codepoint)を評価し、結果をx.valに設定
			case isString(x.typ): // 変換元が既にstringの場合
				// 何もしない (stringからstringへの変換)
			default: // その他の型からのstring変換はエラー
				goto ErrorMsg
			}
		}

このコードブロックは、定数 string(x) 変換の具体的な実装です。

  1. if typ.Kind == String: 変換先の型が string であることを確認します。
  2. case x.isInteger(check.ctxt): 変換元のオペランド x が整数定数である場合にこのケースに入ります。
    • codepoint, ok := x.val.(int64): x の値 (x.val) を int64 型の codepoint として取得しようとします。x.valinterface{} 型であり、定数の実際の値を保持しています。
    • if !ok: x.valint64 に変換できない場合(例えば、非常に大きな整数定数で int64 の範囲を超えている場合)、codepoint-1 に設定します。これは、Goの組み込み string() 関数が負の整数に対してUnicode代替文字 \uFFFD を返す挙動に合わせるためです。
    • x.val = string(codepoint): ここが最も重要な部分です。Goの組み込み string() 関数を呼び出して、codepoint に対応する文字列を生成し、その結果を x.val に設定します。これにより、コンパイル時に string(codepoint) の結果が定数として確定します。
  3. case isString(x.typ): 変換元が既に文字列型である場合、特別な処理は不要です。
  4. default: 上記以外の型(例: true (bool), 1.2 (float), nil)から string への定数変換は許可されないため、ErrorMsg ラベルにジャンプしてエラーを報告します。

この変更により、Goコンパイラは string(65) のような定数式をコンパイル時に "A" と評価できるようになり、より正確な型チェックと最適化が可能になりました。

関連リンク

参考にした情報源リンク

  • GitHubコミットページ: https://github.com/golang/go/commit/0b2caf27177e47b587df6ef4dce2df67ad3e4666
  • Gerrit Code Review (golang.org/cl): https://golang.org/cl/7537043 (このコミットの元のレビューページ)
  • Go言語のIssue #4982 (直接的なリンクは見つかりませんでしたが、コミットメッセージに記載されています)
  • Go言語のソースコード (src/pkg/go/types/)
  • Go言語の仕様書 (The Go Programming Language Specification)
  • Unicode Replacement Character (U+FFFD) に関する情報