[インデックス 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
パッケージなど、パーサー関連のコード)