[インデックス 135] ファイルの概要
このコミットは、Go言語のテストスイートの一部である test/string_lit.go ファイルと、そのテスト結果を記録する test/golden.out ファイルに対する変更を含んでいます。
test/string_lit.go は、Go言語における文字列リテラルの挙動を検証するためのテストファイルです。Go言語では、ダブルクォートで囲まれた「解釈済み文字列リテラル」と、バッククォートで囲まれた「Raw文字列リテラル」の2種類の文字列リテラルが存在し、それぞれ異なるエスケープシーケンスの解釈ルールを持ちます。このテストファイルは、これらの文字列リテラルがGoコンパイラによって正しく解釈されることを確認することを目的としています。
test/golden.out は、Go言語のテストフレームワークにおいて、特定のテストの期待される出力("golden" 出力)を記録するファイルとして使用されることがあります。このファイルへの変更は、string_lit.go のテストが修正された結果、期待される出力が変わったことを示唆しています。
コミット
fix string_lit test to be more thorough
SVN=121623
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f07d2eea52b135575ff77b5446e1f8fe93146e0b
元コミット内容
このコミットの目的は、「文字列リテラルのテストをより徹底的に修正する」ことです。これは、既存のテストが文字列リテラルのすべてのエッジケースや挙動を十分にカバーしていなかったことを示唆しています。コミットメッセージに含まれる SVN=121623 は、この変更がGoプロジェクトの初期段階でSubversionリポジトリからGitへの移行時に記録された、元のSubversionリビジョン番号であることを示しています。
変更の背景
Go言語の初期開発段階において、言語仕様の策定と同時に、その実装が仕様通りに動作するかを検証するためのテストスイートが構築されていました。文字列リテラルは、プログラミング言語において非常に基本的な要素であり、その解釈は厳密でなければなりません。エスケープシーケンスの処理、Unicode文字のサポート、異なる種類の文字列リテラル(解釈済みとRaw)の挙動など、多くの側面を考慮する必要があります。
このコミットが行われた背景には、おそらく既存の string_lit.go テストが、文字列リテラルの特定のケース(例えば、特定の不正なエスケープシーケンス、Unicodeエスケープ、Raw文字列リテラル内のバックスラッシュなど)を十分に検証できていなかった、あるいは誤った結果を報告していたという問題があったと考えられます。より「徹底的な」テストを追加することで、コンパイラが文字列リテラルを正しく処理することを保証し、将来的なバグを防ぐことが目的です。
test/golden.out からの変更(特にエラーメッセージの削除)は、以前はテストが失敗していたか、あるいはコンパイラが誤ったエラーを報告していたケースが、このコミットによって修正され、テストが成功するようになったことを示唆しています。
前提知識の解説
Go言語の文字列リテラル
Go言語には、主に2種類の文字列リテラルがあります。
-
解釈済み文字列リテラル (Interpreted String Literals):
- ダブルクォート (
") で囲まれます。 - バックスラッシュ (
\) を使ったエスケープシーケンスが解釈されます。\a: ベル\b: バックスペース\f: フォームフィード\n: 改行\r: キャリッジリターン\t: 水平タブ\v: 垂直タブ\\: バックスラッシュ\": ダブルクォート\': シングルクォート\ooo: 8進数エスケープ(例:\000はヌル文字)\xhh: 16進数エスケープ(例:\x61は 'a')\uhhhh: 16進数Unicodeエスケープ(例:\u00e4は 'ä')\Uhhhhhhhh: 32進数Unicodeエスケープ(例:\U0000babe)
- 改行を含めることはできません(改行を含める場合はRaw文字列リテラルを使用)。
- ダブルクォート (
-
Raw文字列リテラル (Raw String Literals):
- バッククォート (
`) で囲まれます。 - エスケープシーケンスは解釈されず、バッククォート内の文字はすべてそのままの文字として扱われます。
- 改行を含めることができます。
- 主に、正規表現、HTML、JSONなどの複数行にわたる文字列や、バックスラッシュを頻繁に使用する文字列を記述する際に便利です。
- バッククォート (
テストにおける assert 関数の役割
プログラミングにおけるテストでは、期待される結果と実際の結果を比較し、それらが一致しない場合にエラーを報告するメカニズムが不可欠です。このコミットで導入された assert 関数は、まさにその役割を担っています。
assert(actual, expected, message) のような形式の関数は、actual と expected の値が異なる場合に、message と共に詳細なエラー情報を出力し、テストの失敗を示すフラグ(この場合は ecode)を設定します。これにより、どのテストが、なぜ失敗したのかを明確に把握することができます。特に文字列の比較においては、文字ごとの違いを詳細に表示することで、デバッグを容易にします。
技術的詳細
このコミットの主要な変更点は、test/string_lit.go ファイルにおける文字列リテラルのテストの網羅性を高めることです。
-
assert関数の導入:- 以前のテストは、単に文字列リテラルを宣言するだけでしたが、このコミットでは
assertというヘルパー関数が導入されました。 - この
assert関数は、2つの文字列が等しいかどうかを比較し、等しくない場合にはエラーメッセージと、文字列のどの位置で文字が異なるかを詳細に表示します。これにより、テストの失敗原因を特定しやすくなります。 ecodeというグローバル変数が導入され、テストが失敗した際に1に設定されます。main関数はこのecodeの値を返します。これは、テストが成功したか失敗したかをシェルスクリプトなどから判断するための一般的な方法です(Unix系システムでは、0は成功、非0は失敗を示す)。
- 以前のテストは、単に文字列リテラルを宣言するだけでしたが、このコミットでは
-
テストケースの拡充:
- 以前は
[]string(...)のように文字列リテラルをリストアップするだけでしたが、新しいコードではassert関数を使用して、各文字列リテラルが期待通りの値に評価されることを明示的に検証しています。 - 様々な種類の文字列リテラルがテストされています。
- 空文字列 (
"",) - 空白を含む文字列 (
" ",` `) - クォートを含む文字列 (
"'",`'"`) - 基本的なASCII文字 (
"a",`a`) - Unicode文字 (
"ä",`ä`,"本",`本`) - 解釈済み文字列リテラルにおける各種エスケープシーケンス (
\a,\b,\f,\n,\r,\t,\v,\\,\") - Raw文字列リテラルにおけるエスケープシーケンスの非解釈挙動 (
\a\b\f\n\r\t\v\\\\\') - 8進数、16進数、Unicodeエスケープシーケンス (
\000,\123,\x00,\xca,\xFE,\u0123,\ubabe,\U0000babe) - Raw文字列リテラルにおける不正なエスケープシーケンス(
\x,\u,\U,\)の挙動。Raw文字列ではこれらはエスケープシーケンスとして解釈されず、そのままの文字として扱われるべきです。
- 空文字列 (
- 以前は
-
test/golden.outの変更:string_lit.go:5: syntax errorやstring_lit.go:12: unknown escape sequence: \といったエラーメッセージがgolden.outから削除されています。これは、これらのエラーが以前はテストの期待される出力の一部であったが、コードの修正によってこれらのエラーが解消された(つまり、コンパイラが正しく文字列リテラルを解釈するようになった)ことを示しています。
これらの変更により、Goコンパイラが文字列リテラルを正しくパースし、評価できることの信頼性が大幅に向上しました。
コアとなるコードの変更箇所
test/string_lit.go
// 変更前 (抜粋)
func main() {
[]string(
"",
" ",
"\'`",
// ...
`\x\u\U\`
);
}
// 変更後 (抜粋)
var ecode int;
func assert(a, b, c string) {
if a != b {
ecode = 1;
print "FAIL: ", c, ": ", a, "!=", b, "\n";
var max int = len(a);
if len(b) > max {
max = len(b);
}
for i := 0; i < max; i++ {
ac := 0;
bc := 0;
if i < len(a) {
ac = int(a[i]);
}
if i < len(b) {
bc = int(b[i]);
}
if ac != bc {
print "\ta[", i, "] = ", ac, "; b[", i, "] =", bc, "\n";
}
}
}
}
func main() int {
ecode = 0;
s := // この変数は実際には使用されず、文字列リテラルの宣言のみ
""
" "
"\'`"
"a"
"ä"
"本"
"\\a\\b\\f\\n\\r\\t\\v\\\\\\\""
"\\000\\123\\x00\\xca\\xFE\\u0123\\ubabe\\U0000babe" // U0123ABCD, Ucafebabe は U0000babe に変更
``
` `
`\'"`
`a`
`ä`
`本`
`\\a\\b\\f\\n\\r\\t\\v\\\\\\'`
`\\000\\123\\x00\\xca\\xFE\\u0123\\ubabe\\U0000babe`
`\\x\\u\\U\\`
;
assert("", ``, "empty");
assert(" ", " ", "blank");
assert("\\x61", "a", "lowercase a");
assert("\\x61", `a`, "lowercase a (backquote)");
assert("\\u00e4", "ä", "a umlaut");
assert("\\u00e4", `ä`, "a umlaut (backquote)");
assert("\\u672c", "本", "nihon");
assert("\\u672c", `本`, "nihon (backquote)");
assert("\\x07\\x08\\x0c\\x0a\\x0d\\x09\\x0b\\x5c\\x22",
"\\a\\b\\f\\n\\r\\t\\v\\\\\\\"",
"backslashes");
assert("\\\\a\\\\b\\\\f\\\\n\\\\r\\\\t\\\\v\\\\\\\\\\\\\\\"",
`\a\b\f\n\r\t\v\\\"`,
"backslashes (backquote)");
assert("\\x00\\x53\\000\\xca\\376S몾몾", // 実際の出力は \\x00\\x53\\x00\\xca\\xfe\\u0053\\ubabe\\U0000babe
"\\000\\123\\x00\\312\\xFE\\u0053\\ubabe\\U0000babe",
"backslashes 2");
assert("\\\\000\\\\123\\\\x00\\\\312\\\\xFE\\\\u0123\\\\ubabe\\\\U0000babe",
`\000\123\x00\312\xFE\u0123\ubabe\U0000babe`,
"backslashes 2 (backquote)");
assert("\\\\x\\\\u\\\\U\\\\", `\x\u\U\`, "backslash 3 (backquote)");
return ecode;
}
test/golden.out
--- a/test/golden.out
+++ b/test/golden.out
@@ -41,9 +41,6 @@ sieve.go:8: fatal error: walktype: switch 1 unknown op SEND l(8)
BUG: known to fail incorrectly
=========== ./string_lit.go
--string_lit.go:5: syntax error
--string_lit.go:12: unknown escape sequence: \
-BUG: known to fail incorrectly
=========== ./switch.go
コアとなるコードの解説
assert 関数
assert 関数は、テストの検証ロジックをカプセル化しています。
a(actual) とb(expected) の2つの文字列を比較します。- もし
aとbが異なる場合、ecodeグローバル変数を1に設定し、テストが失敗したことを示します。 printステートメントを使用して、どのテストが失敗したか (cはテストの説明文字列)、そしてaとbの具体的な値を出力します。- さらに、
forループを使って文字列aとbを文字ごとに比較し、異なる文字のインデックスとASCII/Unicode値を詳細に出力します。これにより、文字列の比較エラーのデバッグが非常に容易になります。
main 関数
main 関数は、文字列リテラルのテストケースを実行するエントリポイントです。
ecode = 0;で初期化され、テストが成功した場合は0が返されます。s := ...のブロックは、様々な文字列リテラルを宣言していますが、この変数s自体は後続のコードでは使用されません。これは、コンパイラがこれらの文字列リテラルを正しくパースできることを確認するためのものです。- その後の
assert関数の呼び出しが、実際のテストロジックです。各assert呼び出しは、特定の文字列リテラルが期待される値に評価されることを検証します。- 例えば、
assert("a", "a", "lowercase a");は、文字列リテラル"a"が文字 'a' に評価されることを確認します。 assert("\\u00e4", "ä", "a umlaut");は、Unicodeエスケープシーケンス\u00e4がウムラウト付きの 'a' に評価されることを確認します。- Raw文字列リテラルについても同様に、エスケープシーケンスが解釈されないことを確認するテストが含まれています。例えば、
assert("\\\\a\\\\b\\\\f\\\\n\\\\r\\\\t\\\\v\\\\\\\\\\\\\\\"",\a\b\f\n\r\t\v\", "backslashes (backquote)");は、Raw文字列リテラル内のバックスラッシュがそのままの文字として扱われることを検証しています。
- 例えば、
test/golden.out の変更
test/golden.out からのエラーメッセージの削除は、string_lit.go の変更によって、以前はコンパイラが誤って構文エラーや不明なエスケープシーケンスエラーを報告していた問題が解決されたことを意味します。これは、Goコンパイラの文字列リテラルパーサーがより堅牢になったことを示しています。
関連リンク
- Go言語の仕様 - 文字列リテラル: https://go.dev/ref/spec#String_literals (最新の仕様ですが、基本的な概念は当時から大きく変わっていません)
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード (特に
go/src/cmd/compile/internal/syntaxパッケージなど、パーサー関連のコード)