[インデックス 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)
型変換を実装するものです。特に、整数 x
を string(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 + 2
の C
は 3
という定数になります。
型変換 (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)
変換が未実装であることが示されていました。このコミットでは、この部分に具体的なロジックが追加されました。
追加されたロジックは以下のステップで動作します。
- 定数変換の検出: 変換元のオペランド
x
が定数 (x.mode == constant
) であり、変換先の型typ
も定数型 (isConstType(typ)
) である場合に、定数変換のパスに入ります。 string(x)
変換の特定: 変換先の型がstring
型であるかをチェックします (typ.Kind == String
)。- 整数からの変換:
- 変換元
x
が整数型である場合 (x.isInteger(check.ctxt)
)、x.val
からint64
型のcodepoint
を抽出します。 codepoint
の抽出に失敗した場合(例: 値がint64
の範囲を超えている、または不明な場合)、codepoint
を-1
に設定します。これは、Goの組み込みstring()
関数が負の整数に対して\uFFFD
を返す挙動を模倣するためです。x.val
をstring(codepoint)
の結果で更新します。これにより、コンパイル時に文字列定数が生成されます。
- 変換元
- 文字列からの変換:
- 変換元
x
が既に文字列型である場合 (isString(x.typ)
)、特別な処理は不要です(string
からstring
への変換は自明)。
- 変換元
- その他の型からの変換:
- 上記以外の型(例:
bool
,float
,nil
)からstring
への定数変換は許可されません。これらのケースではgoto ErrorMsg
を使用してエラーパスに分岐し、「cannot convert %s to %s」というエラーメッセージを生成します。
- 上記以外の型(例:
- エラー処理の改善:
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)
変換の具体的な実装です。
if typ.Kind == String
: 変換先の型がstring
であることを確認します。case x.isInteger(check.ctxt)
: 変換元のオペランドx
が整数定数である場合にこのケースに入ります。codepoint, ok := x.val.(int64)
:x
の値 (x.val
) をint64
型のcodepoint
として取得しようとします。x.val
はinterface{}
型であり、定数の実際の値を保持しています。if !ok
:x.val
がint64
に変換できない場合(例えば、非常に大きな整数定数でint64
の範囲を超えている場合)、codepoint
を-1
に設定します。これは、Goの組み込みstring()
関数が負の整数に対してUnicode代替文字\uFFFD
を返す挙動に合わせるためです。x.val = string(codepoint)
: ここが最も重要な部分です。Goの組み込みstring()
関数を呼び出して、codepoint
に対応する文字列を生成し、その結果をx.val
に設定します。これにより、コンパイル時にstring(codepoint)
の結果が定数として確定します。
case isString(x.typ)
: 変換元が既に文字列型である場合、特別な処理は不要です。default
: 上記以外の型(例:true
(bool),1.2
(float),nil
)からstring
への定数変換は許可されないため、ErrorMsg
ラベルにジャンプしてエラーを報告します。
この変更により、Goコンパイラは string(65)
のような定数式をコンパイル時に "A"
と評価できるようになり、より正確な型チェックと最適化が可能になりました。
関連リンク
- Go言語の型システムに関するドキュメント: https://go.dev/blog/go-type-system (一般的な情報)
- Go言語の定数に関するドキュメント: https://go.dev/ref/spec#Constants
- Go言語の型変換に関するドキュメント: https://go.dev/ref/spec#Conversions
- Go言語の
string
型とUnicodeに関するドキュメント: https://go.dev/blog/strings
参考にした情報源リンク
- 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) に関する情報