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

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

このコミットは、Go言語の初期のコード整形ツールである usr/gri/pretty/printer.go ファイルに対するものです。このファイルは、Go言語のソースコードを読み込み、整形された出力("pretty print")を生成する役割を担っていました。具体的には、構文木(AST: Abstract Syntax Tree)を走査し、適切なインデント、改行、そしてセミコロンなどの区切り文字を挿入して、読みやすいコードを生成するロジックが含まれています。

コミット

このコミットは、Go言語のコード整形ツールにおけるバグ修正です。具体的には、ラベルの後に不必要なセミコロンが挿入される問題を修正し、また、セミコロンが必須であるべき場合に正しく挿入されない問題を改善しています。

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

https://github.com/golang/go/commit/fadf159a8190b0b4dee3d508056dd30e4f5698de

元コミット内容

fix a bug: do not print a ';' after a label if there wasn't one

R=r
OCL=25526
CL=25528
---
 usr/gri/pretty/printer.go | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/usr/gri/pretty/printer.go b/usr/gri/pretty/printer.go
index 8f270216ef..f08e70ccdb 100644
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -760,7 +760,7 @@ func (P *Printer) StatementList(list *vector.Vector) {
 		if i == 0 {
 			P.newlines = 1;
 		} else {  // i > 0
-			if !P.opt_semi {
+			if !P.opt_semi || *optsemicolons {
 				// semicolon is required
 				P.separator = semicolon;
 			}
@@ -806,6 +806,10 @@ func (P *Printer) DoLabelDecl(s *AST.LabelDecl) {
 	P.indentation--;
 	P.Expr(s.Label);\n \tP.Token(s.Pos, Scanner.COLON);\n+\t// TODO not quite correct:\n+\t// - we must not print this optional semicolon, as it may invalidate code.\n+\t// - this will change once the AST reflects the LabelStatement change\n+\tP.opt_semi = true;\n \tP.indentation++;
 }\n \n

変更の背景

このコミットが行われた2009年2月は、Go言語がまだ一般に公開される前の非常に初期の開発段階でした。この時期のGo言語は、その構文やセマンティクスが活発に議論され、変更されていました。

このコミットの背景には、主に以下の2つの問題があったと考えられます。

  1. ラベル後の不適切なセミコロン挿入: Go言語において、ラベル(label:)の後にセミコロンを置くことは文法的に誤りであり、コードを無効にしてしまいます。しかし、当時のprinter.goは、特定の状況下でラベルの後に自動的にセミコロンを挿入してしまうバグを抱えていた可能性があります。これは、コード整形ツールが意図せず無効なコードを生成してしまうという重大な問題です。
  2. セミコロン挿入ロジックの不備: Go言語には「自動セミコロン挿入 (Automatic Semicolon Insertion: ASI)」という特徴がありますが、このコミットの時点ではそのルールが完全に確立・実装されていなかったか、あるいはprinter.goのセミコロン挿入ロジックが不完全であった可能性があります。特に、セミコロンが必須であるべき状況(例えば、*optsemicolonsというグローバルな設定でセミコロンが常に必要とされている場合)において、printer.goがセミコロンを省略してしまうバグがあったと考えられます。

これらの問題は、Go言語のコードの正確な整形と、その後のコンパイル可能性に直接影響するため、修正が急務でした。

前提知識の解説

Go言語の初期開発

このコミットは2009年2月に行われており、Go言語が一般に公開された2009年11月よりも前の時期にあたります。この段階では、Go言語の仕様は流動的であり、言語設計者(Robert Griesemer氏もその一人)が直接コードベースに手を加えて、言語の挙動やツールの機能を試行錯誤していました。usr/gri/というパスは、Robert Griesemer氏の作業ディレクトリを示唆しており、当時の開発スタイルを反映しています。

Pretty Printer (コード整形ツール)

Pretty Printerとは、ソースコードを読み込み、一貫したスタイル(インデント、スペース、改行など)で整形して出力するツールの総称です。プログラミング言語のコンパイラやインタプリタは、通常、ソースコードを構文木(AST)に変換して処理します。Pretty Printerは、このASTを再びソースコードに変換する際に、人間が読みやすいように整形する役割を担います。Go言語においては、gofmtが公式のコード整形ツールとして広く使われていますが、このコミットのprinter.goはその前身、あるいは開発初期の実験的な実装であったと考えられます。

Go言語におけるセミコロン

Go言語は、C言語やJavaのような言語とは異なり、文の終わりに明示的にセミコロン(;)を記述する必要がありません。これは「自動セミコロン挿入 (Automatic Semicolon Insertion: ASI)」というメカニズムによるものです。Goのコンパイラは、特定のルールに基づいて改行位置に自動的にセミコロンを挿入します。

ASIの基本的なルールは以下の通りです。

  • 改行の直前が識別子、整数リテラル、浮動小数点リテラル、虚数リテラル、ルーンリテラル、文字列リテラル、キーワード(break, continue, fallthrough, return)、演算子や区切り文字(++, --, ), ], })である場合、その改行の後にセミコロンが挿入されます。
  • これにより、多くのGoコードではセミコロンを明示的に書く必要がなくなります。

しかし、このコミットの時点では、ASIのルールが完全に固まっていなかったか、あるいはprinter.goがそのルールを完全に実装していなかった可能性があります。

ラベル (Labels)

プログラミング言語におけるラベルは、コード内の特定の場所を識別するための名前です。Go言語では、主にgoto文のジャンプ先として、またbreak文やcontinue文でネストされたループやswitch文、select文から脱出する際に使用されます。

例:

func myFunc() {
myLabel:
    for i := 0; i < 10; i++ {
        if i == 5 {
            goto myLabel // myLabelへジャンプ
        }
    }
}

Go言語の文法では、ラベルの宣言(myLabel:)の直後にセミコロンを置くことはできません。ラベルの後に続くのは、通常、別の文やブロックです。

技術的詳細

このコミットは、usr/gri/pretty/printer.goファイル内の2つの主要な関数、StatementListDoLabelDeclに変更を加えています。

StatementList関数における変更

StatementList関数は、複数の文のリストを整形して出力する役割を担っています。この関数内で、セミコロンを挿入するかどうかのロジックが変更されました。

変更前:

if !P.opt_semi {
    // semicolon is required
    P.separator = semicolon;
}

変更後:

if !P.opt_semi || *optsemicolons {
    // semicolon is required
    P.separator = semicolon;
}
  • P.opt_semi: これはPrinter構造体のフィールドで、現在の位置でセミコロンが「オプション」であるかどうかを示すフラグです。trueであればセミコロンはオプション(つまり、挿入しなくてもよい)、falseであればセミコロンは必須です。
  • *optsemicolons: これはグローバルな設定、おそらくコマンドライン引数や設定ファイルから読み込まれるブール値のポインタで、セミコロンが常に必須であるかどうかを制御します。trueであれば、セミコロンは常に必須と見なされます。

変更の意図は、セミコロンが必須となる条件を拡張することです。

  • 変更前は、「現在の位置でセミコロンが必須である場合(!P.opt_semi)」にのみセミコロンが挿入されていました。
  • 変更後は、「現在の位置でセミコロンが必須である場合(!P.opt_semi)」または「グローバル設定でセミコロンが常に必須とされている場合(*optsemicolons)」にセミコロンが挿入されるようになりました。

これにより、グローバル設定でセミコロンが必須とされているにもかかわらず、P.opt_semitrue(オプション)であるためにセミコロンが省略されてしまうというバグが修正されました。

DoLabelDecl関数における変更

DoLabelDecl関数は、ラベル宣言(例: myLabel:)を整形して出力する役割を担っています。

追加された行:

P.opt_semi = true;

この行は、ラベル宣言の処理が完了した直後に追加されました。この変更の目的は、ラベルの直後に不必要なセミコロンが挿入されるのを防ぐことです。

  • P.opt_semi = true;と設定することで、StatementList関数などの後続の処理に対して、「この位置ではセミコロンはオプションである」と伝えます。
  • これにより、printer.goがラベルの後に自動的にセミコロンを挿入してしまうことを防ぎます。Go言語の文法では、ラベルの後にセミコロンは不要であり、むしろエラーとなるため、この修正は重要です。

TODOコメントの重要性

追加されたTODOコメントは、この修正が一時的なものであることを示唆しています。

// TODO not quite correct:
// - we must not print this optional semicolon, as it may invalidate code.
// - this will change once the AST reflects the LabelStatement change

このコメントは、当時のGo言語のAST(抽象構文木)が、ラベル宣言とラベルを含む文(LabelStatement)を十分に区別できていなかった可能性を示しています。そのため、P.opt_semi = trueという設定は、ASTの不完全さを補うための暫定的な回避策であり、将来的にはASTの構造が改善されれば、より正確な方法でこの問題が解決されることを示唆しています。これは、Go言語の初期開発における試行錯誤の過程を垣間見ることができます。

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

diff --git a/usr/gri/pretty/printer.go b/usr/gri/pretty/printer.go
index 8f270216ef..f08e70ccdb 100644
--- a/usr/gri/pretty/printer.go
+++ b/usr/gri/pretty/printer.go
@@ -760,7 +760,7 @@ func (P *Printer) StatementList(list *vector.Vector) {
 		if i == 0 {
 			P.newlines = 1;
 		} else {  // i > 0
-			if !P.opt_semi {
+			if !P.opt_semi || *optsemicolons {
 				// semicolon is required
 				P.separator = semicolon;
 			}
@@ -806,6 +806,10 @@ func (P *Printer) DoLabelDecl(s *AST.LabelDecl) {\n \tP.indentation--;
 	P.Expr(s.Label);\n \tP.Token(s.Pos, Scanner.COLON);\n+\t// TODO not quite correct:\n+\t// - we must not print this optional semicolon, as it may invalidate code.\n+\t// - this will change once the AST reflects the LabelStatement change\n+\tP.opt_semi = true;\n \tP.indentation++;
 }\n \n

コアとなるコードの解説

StatementList関数内の変更

  • 変更前: if !P.opt_semi {
    • これは、「現在の文脈でセミコロンが必須である場合」にのみ、セミコロンを挿入するロジックでした。
  • 変更後: if !P.opt_semi || *optsemicolons {
    • この変更により、セミコロン挿入の条件が拡張されました。
    • !P.opt_semi(現在の文脈でセミコロンが必須)に加えて、*optsemicolons(グローバル設定でセミコロンが常に必須)という条件が||(OR)で追加されました。
    • これにより、たとえP.opt_semitrue(セミコロンはオプション)であっても、*optsemicolonstrue(セミコロンは常に必須)であれば、セミコロンが正しく挿入されるようになりました。これは、グローバルなセミコロン挿入ポリシーを尊重するための修正です。

DoLabelDecl関数内の追加

  • P.opt_semi = true;
    • この行は、ラベル宣言(例: myLabel:)の処理が完了した直後に挿入されました。
    • P.opt_semitrueに設定することで、printerはラベルの直後ではセミコロンが「オプション」であると認識します。
    • Go言語の文法ではラベルの直後にセミコロンは不要であり、挿入すると構文エラーになるため、この設定により、printerが誤ってセミコロンを挿入してしまうことを防ぎます。
    • TODOコメントが示すように、これは当時のASTの限界を補うための暫定的な解決策であり、ラベルの後にセミコロンを挿入しないという意図を明示的にprinterに伝えるためのものです。

これらの変更は、Go言語のコード整形ツールが、言語の文法規則(特にセミコロンの自動挿入とラベルの扱い)をより正確に反映し、有効なGoコードを生成できるようにするための重要なバグ修正でした。

関連リンク

  • Go言語の公式ドキュメント: https://go.dev/
  • Go言語の仕様 (The Go Programming Language Specification): https://go.dev/ref/spec
    • 特に「Semicolons」のセクションが関連します。

参考にした情報源リンク

  • Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
  • Go言語の初期開発に関する議論やメーリングリストのアーカイブ(一般公開されていない可能性が高いですが、当時の開発状況を理解する上で重要です)
  • Go言語の自動セミコロン挿入 (ASI) に関する記事や解説(Go 1.0以降のものが主ですが、概念理解に役立ちます)