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

[インデックス 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種類の文字列リテラルがあります。

  1. 解釈済み文字列リテラル (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文字列リテラルを使用)。
  2. Raw文字列リテラル (Raw String Literals):

    • バッククォート (`) で囲まれます。
    • エスケープシーケンスは解釈されず、バッククォート内の文字はすべてそのままの文字として扱われます。
    • 改行を含めることができます。
    • 主に、正規表現、HTML、JSONなどの複数行にわたる文字列や、バックスラッシュを頻繁に使用する文字列を記述する際に便利です。

テストにおける assert 関数の役割

プログラミングにおけるテストでは、期待される結果と実際の結果を比較し、それらが一致しない場合にエラーを報告するメカニズムが不可欠です。このコミットで導入された assert 関数は、まさにその役割を担っています。

assert(actual, expected, message) のような形式の関数は、actualexpected の値が異なる場合に、message と共に詳細なエラー情報を出力し、テストの失敗を示すフラグ(この場合は ecode)を設定します。これにより、どのテストが、なぜ失敗したのかを明確に把握することができます。特に文字列の比較においては、文字ごとの違いを詳細に表示することで、デバッグを容易にします。

技術的詳細

このコミットの主要な変更点は、test/string_lit.go ファイルにおける文字列リテラルのテストの網羅性を高めることです。

  1. assert 関数の導入:

    • 以前のテストは、単に文字列リテラルを宣言するだけでしたが、このコミットでは assert というヘルパー関数が導入されました。
    • この assert 関数は、2つの文字列が等しいかどうかを比較し、等しくない場合にはエラーメッセージと、文字列のどの位置で文字が異なるかを詳細に表示します。これにより、テストの失敗原因を特定しやすくなります。
    • ecode というグローバル変数が導入され、テストが失敗した際に 1 に設定されます。main 関数はこの ecode の値を返します。これは、テストが成功したか失敗したかをシェルスクリプトなどから判断するための一般的な方法です(Unix系システムでは、0は成功、非0は失敗を示す)。
  2. テストケースの拡充:

    • 以前は []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文字列ではこれらはエスケープシーケンスとして解釈されず、そのままの文字として扱われるべきです。
  3. test/golden.out の変更:

    • string_lit.go:5: syntax errorstring_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つの文字列を比較します。
  • もし ab が異なる場合、ecode グローバル変数を 1 に設定し、テストが失敗したことを示します。
  • print ステートメントを使用して、どのテストが失敗したか (c はテストの説明文字列)、そして ab の具体的な値を出力します。
  • さらに、for ループを使って文字列 ab を文字ごとに比較し、異なる文字のインデックスと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言語の公式ドキュメント
  • Go言語のソースコード (特に go/src/cmd/compile/internal/syntax パッケージなど、パーサー関連のコード)