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

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

このコミットは、Go言語のコンパイラ(gc)の一部であるsrc/cmd/gc/typecheck.cファイルに対する変更です。typecheck.cは、Goコンパイラの型チェックフェーズにおいて重要な役割を担っており、特にマップリテラル(map[key]value{...})の定義時にキーの重複がないかを検証するロジックを含んでいます。

コミット

このコミットは、Go言語のコンパイラがマップリテラル内で重複するキーを検出した際のエラーメッセージを改善することを目的としています。具体的には、エラーメッセージに重複しているキーの具体的な値を表示するように変更されました。これにより、開発者はどのキーが重複しているのかを即座に把握できるようになり、デバッグの効率が向上します。

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

https://github.com/golang/go/commit/5ab9d2befd7c37468121081a84188aa687678e13

元コミット内容

cmd/gc: show duplicate key in error

R=ken2
CC=golang-dev
https://golang.org/cl/5728064

変更の背景

Go言語のマップ(map)は、キーと値のペアを格納するデータ構造であり、キーは一意である必要があります。もしマップリテラルを定義する際に同じキーを複数回指定した場合、それはGo言語の仕様違反となり、コンパイル時にエラーが発生します。

このコミットが行われる前は、重複キーが検出された際のエラーメッセージは「duplicate key in map literal」(マップリテラル内でキーが重複しています)という汎用的なものでした。このメッセージだけでは、どのキーが重複しているのかが不明であり、特に大規模なマップリテラルや複雑な式でキーが生成されている場合、開発者は重複箇所を特定するためにコードを詳細に調査する必要がありました。

この変更の背景には、開発者のデバッグ体験を向上させ、エラーメッセージの有用性を高めるという明確な意図があります。エラーメッセージに具体的な重複キーを含めることで、問題の特定と解決にかかる時間を大幅に短縮できます。これは、コンパイラが提供する診断情報の品質を向上させるための典型的な改善です。

前提知識の解説

Go言語のマップリテラル

Go言語では、マップは以下のように定義され、初期化されます。

m := map[string]int{
    "apple":  1,
    "banana": 2,
    "orange": 3,
}

マップリテラル内でキーは一意でなければなりません。例えば、以下のようなコードはコンパイルエラーになります。

m := map[string]int{
    "apple": 1,
    "banana": 2,
    "apple": 3, // "apple" が重複
}

Goコンパイラ (gc)

Go言語の公式コンパイラはgc(Go Compiler)と呼ばれます。gcは、Goのソースコードを機械語に変換する役割を担っています。コンパイラは複数のフェーズ(字句解析、構文解析、型チェック、最適化、コード生成など)を経て処理を進めます。

  • cmd/gc: Goのソースコードリポジトリにおいて、cmd/gcディレクトリはgcコンパイラの主要なソースコードを含んでいます。
  • src/cmd/gc/typecheck.c: このファイルは、gcコンパイラの「型チェック」フェーズの一部を実装しています。型チェックは、プログラムがGo言語の型システム規則に準拠していることを確認するプロセスです。これには、変数の型の一致、関数の引数と戻り値の型、そして今回のケースのようにマップリテラルのキーの一意性などの検証が含まれます。

yyerror関数

yyerrorは、Goコンパイラ(および多くのC言語ベースのコンパイラやパーサ)でエラーメッセージを出力するために使用されるユーティリティ関数です。この関数は、C言語のprintf関数と同様に、フォーマット文字列とそれに続く可変個の引数を受け取ることができます。これにより、動的なエラーメッセージを生成することが可能です。

Node構造体と%Nフォーマット指定子

Goコンパイラの内部では、ソースコードは抽象構文木(AST: Abstract Syntax Tree)として表現されます。ASTの各要素はNode構造体で表されます。例えば、変数、リテラル、演算子、関数呼び出しなどはすべてNodeとして扱われます。

yyerror関数で使用される%Nは、Goコンパイラ特有のフォーマット指定子です。これは、通常のprintf%d(整数)、%s(文字列)などとは異なり、Node構造体を人間が読める形式(例えば、リテラルの値や識別子の名前)に変換して出力するためにコンパイラ内部で特別に処理されます。この機能により、コンパイラはエラーメッセージにASTの具体的な要素の情報を埋め込むことができます。

技術的詳細

このコミットの技術的な核心は、src/cmd/gc/typecheck.c内のkeydup関数におけるyyerrorの呼び出し方法の変更です。

keydup関数の役割

keydup関数は、マップリテラルを処理する際に、その中に重複するキーが存在しないかをチェックする役割を担っています。この関数は、マップのキーをハッシュテーブルに格納し、新しいキーを挿入しようとしたときに既に同じキーが存在するかどうかを確認します。もし重複が検出された場合、エラーを報告します。

変更点

変更前は、重複キーが検出された際に以下のコードが実行されていました。

yyerror("duplicate key in map literal");

これは、単に固定の文字列「duplicate key in map literal」を出力するだけでした。

変更後、この行は以下のように修正されました。

yyerror("duplicate key %N in map literal", n);

この変更により、以下の重要な点が導入されました。

  1. %Nフォーマット指定子: yyerrorのフォーマット文字列に%Nが追加されました。これは前述の通り、コンパイラ内部のNode構造体を整形して出力するための特別な指定子です。
  2. n引数: yyerrorの第2引数としてnが渡されています。このnは、keydup関数が重複キーとして検出したNode構造体へのポインタです。つまり、このNodeは重複しているキーのAST表現です。

変更のメカニズムと効果

コンパイラがyyerror("duplicate key %N in map literal", n)を呼び出すと、yyerrorの内部ロジックは%N指定子を認識し、それに続くn引数(Node*型)を解釈します。そして、nが指すNodeの種類に応じて、そのノードが表す具体的な値(例えば、文字列リテラル "foo"、整数リテラル 123、識別子 myKeyなど)を抽出し、エラーメッセージに埋め込みます。

例えば、元のコードが以下のようなものであったとします。

m := map[string]int{
    "foo": 1,
    "bar": 2,
    "foo": 3, // ここで重複
}

変更前は、エラーメッセージは単に「duplicate key in map literal」でした。 変更後は、nが文字列リテラル"foo"を表すNodeとなるため、エラーメッセージは「duplicate key "foo" in map literal」のようになることが期待されます。

この変更は、コンパイラのエラーメッセージの質を飛躍的に向上させ、開発者がコンパイルエラーの原因を迅速に特定し、修正する手助けとなります。

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

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -1964,7 +1964,7 @@ keydup(Node *n, Node *hash[], ulong nhash)
 	\tb = cmp.val.u.bval;
 	\tif(b) {
 	\t\t// too lazy to print the literal
-\t\t\tyyerror("duplicate key in map literal");
+\t\t\tyyerror("duplicate key %N in map literal", n);
 	\t\treturn;
 	\t}\
 	}\

コアとなるコードの解説

変更はsrc/cmd/gc/typecheck.cファイルのkeydup関数内、1964行目付近にあります。

元のコードでは、重複キーが検出された場合にyyerror("duplicate key in map literal");が呼び出されていました。これは、ハードコードされたエラー文字列を出力するだけです。

変更後のコードでは、yyerror("duplicate key %N in map literal", n);に修正されています。

  • %N: これはGoコンパイラが内部的に使用する特別なフォーマット指定子で、Node構造体の内容を人間が読める形式で出力するために使われます。
  • n: keydup関数の引数として渡されるNodeポインタです。このnは、まさに重複が検出されたマップキーを表す抽象構文木のノードです。

この修正により、コンパイラは重複キーのエラーメッセージを生成する際に、単に「重複キーがある」と伝えるだけでなく、「どのキーが重複しているか」という具体的な情報(例: "foo"123など)をエラーメッセージに含めることができるようになりました。これにより、開発者はエラーメッセージを見ただけで問題のキーを特定し、迅速にデバッグを行うことが可能になります。

コメントアウトされていた「// too lazy to print the literal」という行は、以前はリテラルの値をエラーメッセージに含めるのが面倒だった、あるいはそのための適切なメカニズムがなかったことを示唆しています。今回の変更は、その「面倒さ」を解消し、より有用なエラーメッセージを提供するための改善です。

関連リンク

  • Go Gerrit Code Review: https://golang.org/cl/5728064
    • このリンクは、GoプロジェクトのコードレビューシステムであるGerritにおける、このコミットに対応する変更リスト(Change-ID)を示しています。Goプロジェクトでは、コミットがGitHubにプッシュされる前にGerritでレビューが行われます。

参考にした情報源リンク

  • Go言語公式ドキュメント: マップ型に関する情報
  • Goコンパイラのソースコード(src/cmd/gcディレクトリ): yyerror関数やNode構造体の定義、およびtypecheck.cの他の部分のロジックを理解するために参照。
  • Goコンパイラの内部構造に関する一般的な解説記事やドキュメント。```

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

このコミットは、Go言語のコンパイラ(gc)の一部であるsrc/cmd/gc/typecheck.cファイルに対する変更です。typecheck.cは、Goコンパイラの型チェックフェーズにおいて重要な役割を担っており、特にマップリテラル(map[key]value{...})の定義時にキーの重複がないかを検証するロジックを含んでいます。

コミット

このコミットは、Go言語のコンパイラがマップリテラル内で重複するキーを検出した際のエラーメッセージを改善することを目的としています。具体的には、エラーメッセージに重複しているキーの具体的な値を表示するように変更されました。これにより、開発者はどのキーが重複しているのかを即座に把握できるようになり、デバッグの効率が向上します。

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

https://github.com/golang/go/commit/5ab9d2befd7c37468121081a84188aa687678e13

元コミット内容

cmd/gc: show duplicate key in error

R=ken2
CC=golang-dev
https://golang.org/cl/5728064

変更の背景

Go言語のマップ(map)は、キーと値のペアを格納するデータ構造であり、キーは一意である必要があります。もしマップリテラルを定義する際に同じキーを複数回指定した場合、それはGo言語の仕様違反となり、コンパイル時にエラーが発生します。

このコミットが行われる前は、重複キーが検出された際のエラーメッセージは「duplicate key in map literal」(マップリテラル内でキーが重複しています)という汎用的なものでした。このメッセージだけでは、どのキーが重複しているのかが不明であり、特に大規模なマップリテラルや複雑な式でキーが生成されている場合、開発者は重複箇所を特定するためにコードを詳細に調査する必要がありました。

この変更の背景には、開発者のデバッグ体験を向上させ、エラーメッセージの有用性を高めるという明確な意図があります。エラーメッセージに具体的な重複キーを含めることで、問題の特定と解決にかかる時間を大幅に短縮できます。これは、コンパイラが提供する診断情報の品質を向上させるための典型的な改善です。

前提知識の解説

Go言語のマップリテラル

Go言語では、マップは以下のように定義され、初期化されます。

m := map[string]int{
    "apple":  1,
    "banana": 2,
    "orange": 3,
}

マップリテラル内でキーは一意でなければなりません。例えば、以下のようなコードはコンパイルエラーになります。

m := map[string]int{
    "apple": 1,
    "banana": 2,
    "apple": 3, // "apple" が重複
}

Goコンパイラ (gc)

Go言語の公式コンパイラはgc(Go Compiler)と呼ばれます。gcは、Goのソースコードを機械語に変換する役割を担っています。コンパイラは複数のフェーズ(字句解析、構文解析、型チェック、最適化、コード生成など)を経て処理を進めます。

  • cmd/gc: Goのソースコードリポジトリにおいて、cmd/gcディレクトリはgcコンパイラの主要なソースコードを含んでいます。
  • src/cmd/gc/typecheck.c: このファイルは、gcコンパイラの「型チェック」フェーズの一部を実装しています。型チェックは、プログラムがGo言語の型システム規則に準拠していることを確認するプロセスです。これには、変数の型の一致、関数の引数と戻り値の型、そして今回のケースのようにマップリテラルのキーの一意性などの検証が含まれます。

yyerror関数

yyerrorは、Goコンパイラ(および多くのC言語ベースのコンパイラやパーサ)でエラーメッセージを出力するために使用されるユーティリティ関数です。この関数は、C言語のprintf関数と同様に、フォーマット文字列とそれに続く可変個の引数を受け取ることができます。これにより、動的なエラーメッセージを生成することが可能です。

Node構造体と%Nフォーマット指定子

Goコンパイラの内部では、ソースコードは抽象構文木(AST: Abstract Syntax Tree)として表現されます。ASTの各要素はNode構造体で表されます。例えば、変数、リテラル、演算子、関数呼び出しなどはすべてNodeとして扱われます。

yyerror関数で使用される%Nは、Goコンパイラ特有のフォーマット指定子です。これは、通常のprintf%d(整数)、%s(文字列)などとは異なり、Node構造体を人間が読める形式(例えば、リテラルの値や識別子の名前)に変換して出力するためにコンパイラ内部で特別に処理されます。この機能により、コンパイラはエラーメッセージにASTの具体的な要素の情報を埋め込むことができます。

技術的詳細

このコミットの技術的な核心は、src/cmd/gc/typecheck.c内のkeydup関数におけるyyerrorの呼び出し方法の変更です。

keydup関数の役割

keydup関数は、マップリテラルを処理する際に、その中に重複するキーが存在しないかをチェックする役割を担っています。この関数は、マップのキーをハッシュテーブルに格納し、新しいキーを挿入しようとしたときに既に同じキーが存在するかどうかを確認します。もし重複が検出された場合、エラーを報告します。

変更点

変更前は、重複キーが検出された際に以下のコードが実行されていました。

yyerror("duplicate key in map literal");

これは、単に固定の文字列「duplicate key in map literal」を出力するだけでした。

変更後、この行は以下のように修正されました。

yyerror("duplicate key %N in map literal", n);

この変更により、以下の重要な点が導入されました。

  1. %Nフォーマット指定子: yyerrorのフォーマット文字列に%Nが追加されました。これは前述の通り、コンパイラ内部のNode構造体を整形して出力するための特別な指定子です。
  2. n引数: yyerrorの第2引数としてnが渡されています。このnは、keydup関数が重複キーとして検出したNode構造体へのポインタです。つまり、このNodeは重複しているキーのAST表現です。

変更のメカニズムと効果

コンパイラがyyerror("duplicate key %N in map literal", n)を呼び出すと、yyerrorの内部ロジックは%N指定子を認識し、それに続くn引数(Node*型)を解釈します。そして、nが指すNodeの種類に応じて、そのノードが表す具体的な値(例えば、文字列リテラル "foo"、整数リテラル 123、識別子 myKeyなど)を抽出し、エラーメッセージに埋め込みます。

例えば、元のコードが以下のようなものであったとします。

m := map[string]int{
    "foo": 1,
    "bar": 2,
    "foo": 3, // ここで重複
}

変更前は、エラーメッセージは単に「duplicate key in map literal」でした。 変更後は、nが文字列リテラル"foo"を表すNodeとなるため、エラーメッセージは「duplicate key "foo" in map literal」のようになることが期待されます。

この変更は、コンパイラのエラーメッセージの質を飛躍的に向上させ、開発者がコンパイルエラーの原因を迅速に特定し、修正する手助けとなります。

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

--- a/src/cmd/gc/typecheck.c
+++ b/src/cmd/gc/typecheck.c
@@ -1964,7 +1964,7 @@ keydup(Node *n, Node *hash[], ulong nhash)
 	\tb = cmp.val.u.bval;
 	\tif(b) {
 	\t\t// too lazy to print the literal
-\t\t\tyyerror("duplicate key in map literal");
+\t\t\tyyerror("duplicate key %N in map literal", n);
 	\t\treturn;
 	\t}\
 	}\

コアとなるコードの解説

変更はsrc/cmd/gc/typecheck.cファイルのkeydup関数内、1964行目付近にあります。

元のコードでは、重複キーが検出された場合にyyerror("duplicate key in map literal");が呼び出されていました。これは、ハードコードされたエラー文字列を出力するだけです。

変更後のコードでは、yyerror("duplicate key %N in map literal", n);に修正されています。

  • %N: これはGoコンパイラが内部的に使用する特別なフォーマット指定子で、Node構造体の内容を人間が読める形式で出力するために使われます。
  • n: keydup関数の引数として渡されるNodeポインタです。このnは、まさに重複が検出されたマップキーを表す抽象構文木のノードです。

この修正により、コンパイラは重複キーのエラーメッセージを生成する際に、単に「重複キーがある」と伝えるだけでなく、「どのキーが重複しているか」という具体的な情報(例: "foo"123など)をエラーメッセージに含めることができるようになりました。これにより、開発者はエラーメッセージを見ただけで問題のキーを特定し、迅速にデバッグを行うことが可能になります。

コメントアウトされていた「// too lazy to print the literal」という行は、以前はリテラルの値をエラーメッセージに含めるのが面倒だった、あるいはそのための適切なメカニズムがなかったことを示唆しています。今回の変更は、その「面倒さ」を解消し、より有用なエラーメッセージを提供するための改善です。

関連リンク

  • Go Gerrit Code Review: https://golang.org/cl/5728064
    • このリンクは、GoプロジェクトのコードレビューシステムであるGerritにおける、このコミットに対応する変更リスト(Change-ID)を示しています。Goプロジェクトでは、コミットがGitHubにプッシュされる前にGerritでレビューが行われます。

参考にした情報源リンク

  • Go言語公式ドキュメント: マップ型に関する情報
  • Goコンパイラのソースコード(src/cmd/gcディレクトリ): yyerror関数やNode構造体の定義、およびtypecheck.cの他の部分のロジックを理解するために参照。
  • Goコンパイラの内部構造に関する一般的な解説記事やドキュメント。