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

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

このコミットは、Go言語のコンパイラ(gc)において、「ideal bool」(理想的な真偽値)の概念を再導入するものです。これは、以前の変更(CL 5674098)を手動で元に戻す(revert)ものであり、Go言語の型システムにおける真偽値リテラルの扱いを、より柔軟な状態に戻すことを目的としています。具体的には、truefalseといった真偽値リテラルが、特定の型を持たない「理想的な真偽値」として扱われるようになり、明示的な型変換なしに、任意の真偽値型(基底型がboolである型)に代入できるようになります。

コミット

commit a457fa500d35d352a76883706e82fd7e9f8e4bd7
Author: Russ Cox <rsc@golang.org>
Date:   Tue Feb 21 22:54:07 2012 -0500

    gc: return of ideal bool
    
    This is a manual undo of CL 5674098.
    It does not implement the even less strict spec
    that we just agreed on, but it gets us back where
    we were at the last weekly.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/5683069

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

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

元コミット内容

このコミットは、Go言語のコンパイラにおける「ideal bool」の概念を再導入するものです。これは、以前の変更であるCL 5674098を「手動で元に戻す(manual undo)」という形で実施されています。

コミットメッセージによると、この変更は「さらに厳しくない仕様」を完全に実装するものではなく、直近の週次ミーティングで合意された内容に沿って、以前の状態に戻すことを意図しています。これは、Go言語の型システムにおける真偽値リテラルの扱いに関して、一時的に以前の柔軟な挙動を回復させるための措置と考えられます。

具体的には、truefalseといった真偽値リテラルが、コンパイル時に特定の具象型(例: bool)に即座に割り当てられるのではなく、idealboolという「理想的な型」(または「型なし型」)として扱われるようになります。これにより、これらのリテラルは、明示的な型変換なしに、基底型がboolである任意の型(例: type MyBool boolで定義されたMyBool型)に代入できるようになります。

変更の背景

Go言語の型システムには、「型なし定数(untyped constants)」という概念があります。これは、数値リテラル(例: 1003.14)や文字列リテラル(例: "hello")が、初期段階では特定の具象型を持たず、文脈に応じて適切な型に「昇格(promote)」または「変換(convert)」されるというものです。例えば、var i int = 100のように書くと、100は型なしの整数定数として扱われ、int型に適合します。

同様に、真偽値リテラル(truefalse)も、この型なし定数の恩恵を受けるべきであるという議論がありました。しかし、Go言語の初期の設計段階やその後の変更の中で、真偽値リテラルの扱いは揺れ動くことがありました。

このコミットの背景には、CL 5674098という以前の変更が存在します。このCLは、何らかの理由で真偽値リテラルの型推論をより厳格にする、あるいは「ideal bool」の概念を削除する変更であったと推測されます。しかし、その変更がGo言語のユーザーエクスペリエンスや、型システムの意図する柔軟性と合致しない問題を引き起こした可能性があります。

コミットメッセージにある「It does not implement the even less strict spec that we just agreed on, but it gets us back where we were at the last weekly.」という記述から、Go言語の開発チーム内で真偽値リテラルの扱いについて議論があり、より柔軟な("less strict")仕様への移行が合意されたものの、このコミットはまず、以前の「ideal bool」が存在した状態に戻すことで、一時的な安定化を図ったものと理解できます。これは、Go言語のコンパイラ開発における、継続的な改善と、ユーザーからのフィードバックへの対応の一環として行われた変更です。

前提知識の解説

このコミットを理解するためには、以下のGo言語の概念とコンパイラの内部構造に関する知識が必要です。

  1. Go言語の型システム:

    • 具象型(Concrete Types): int, string, boolなどの基本的な型や、struct, interface, map, slice, array, chanなどの複合型。
    • 名前付き型(Named Types): type MyInt intのように、既存の型に新しい名前を付けて定義した型。名前付き型は、基底型が同じでも、異なる型として扱われます。
    • 型なし定数(Untyped Constants): Go言語の数値リテラル(例: 100, 3.14)、文字列リテラル(例: "hello")、真偽値リテラル(例: true, false)は、初期段階では特定の具象型を持ちません。これらは「型なし」として扱われ、文脈に応じて適切な具象型に「昇格」または「変換」されます。これにより、例えばvar i int = 100var f float64 = 100の両方が可能になります。
    • 「理想的な型」(Ideal Types): 型なし定数が持つ、特定の具象型に縛られない抽象的な型。例えば、整数リテラルは「理想的な整数型」、浮動小数点リテラルは「理想的な浮動小数点型」、文字列リテラルは「理想的な文字列型」を持ちます。このコミットでは、「理想的な真偽値型」(idealbool)が導入されます。
  2. Goコンパイラ(gc)の内部構造:

    • src/cmd/gc: Go言語の公式コンパイラのソースコードが格納されているディレクトリ。
    • 字句解析(Lexical Analysis): ソースコードをトークン(単語)に分解するプロセス。src/cmd/gc/lex.cが関連します。
    • 構文解析(Parsing): トークン列から抽象構文木(AST)を構築するプロセス。
    • 型チェック(Type Checking): AST上の各ノードの型を決定し、型の一貫性を検証するプロセス。src/cmd/gc/const.c, src/cmd/gc/subr.cなどが関連します。
    • 定数評価(Constant Evaluation): コンパイル時に定数式を評価し、その結果を定数として扱うプロセス。src/cmd/gc/const.cが関連します。
    • 型表現: コンパイラ内部で型を表現するためのデータ構造。Type構造体や、types配列、idealstringなどのグローバル変数がこれにあたります。
    • CL (Change List): Goプロジェクトがコードレビューに利用しているGerritシステムにおける変更の単位。各コミットは通常、一つのCLに対応します。
  3. Go言語のテストフレームワーク:

    • test/fixedbugs/: 特定のバグ修正を検証するためのテストケースが格納されるディレクトリ。
    • test/: 一般的な言語機能やコンパイラの挙動を検証するためのテストケースが格納されるディレクトリ。

これらの知識を前提として、このコミットがGoコンパイラの字句解析、型チェック、定数評価の各段階でどのように「ideal bool」を導入し、型なし真偽値リテラルの挙動を変更しているかを詳細に見ていきます。

技術的詳細

このコミットの技術的詳細は、「ideal bool」という新しい「理想的な型」をGoコンパイラの内部に導入し、真偽値リテラル(true, false)がこの型を持つように変更することに集約されます。これにより、型なし真偽値リテラルが、文字列リテラルや数値リテラルと同様に、文脈に応じて柔軟に型付けされるようになります。

具体的な変更点は以下の通りです。

  1. idealbool 型の導入:

    • src/cmd/gc/go.h: EXTERN Type* idealbool; が追加され、idealboolというグローバルなTypeポインタが宣言されます。これは、Goコンパイラ全体で「理想的な真偽値型」を表すために使用されます。
    • src/cmd/gc/lex.c: lexinit関数内で idealbool = typ(TBOOL); が追加されます。これは、idealboolが内部的にはTBOOL(Goの組み込みbool型)を基底とする型として初期化されることを意味します。しかし、その振る舞いは通常のTBOOLとは異なり、型なし定数としての特性を持ちます。
    • src/cmd/gc/lex.c: truefalseの組み込みシンボル(builtinpkg内のtruefalse)の定義において、その型がtypes[TBOOL]からidealboolに変更されます。これにより、ソースコード中のtruefalseというリテラルは、字句解析の段階でidealbool型を持つノードとして扱われるようになります。
  2. 定数評価と型推論の変更:

    • src/cmd/gc/const.c:
      • nodlit関数(リテラルノードを作成する関数)において、CTBOOL(真偽値定数)の場合に、ノードの型をtypes[TBOOL]からidealboolに設定するよう変更されます。
      • defaultlit関数(型なしリテラルにデフォルトの型を割り当てる関数)において、ノードの型がtypes[TBOOL]であるかどうかのチェックがidealboolであるかどうかのチェックに変更されます。これは、idealboolが型なし定数として扱われ、文脈に応じて具象型に変換されることを示唆しています。
    • src/cmd/gc/subr.c:
      • nodbool関数(真偽値ノードを作成する関数)において、作成されるノードの型がtypes[TBOOL]からidealboolに変更されます。
      • isideal関数(ある型が「理想的な型」であるかを判定する関数)において、t == idealboolの条件が追加されます。これにより、idealboolidealstringと同様に「理想的な型」として認識されるようになります。
  3. 型チェックとエクスポートの変更:

    • src/cmd/gc/export.c: reexportdep関数において、型がidealboolであるかどうかのチェックが追加されます。これは、コンパイラが型情報をエクスポートする際に、idealboolを適切に処理する必要があることを示しています。
    • src/cmd/gc/fmt.c:
      • typefmt関数(型をフォーマットする関数)において、エラーモード(FErr)でidealbool型が検出された場合に、「ideal 」という接頭辞を付けて表示するよう変更されます。これはデバッグやエラーメッセージの表示に役立ちます。
      • exprfmt関数(式をフォーマットする関数)において、ノードの型がidealboolであるかどうかのチェックが追加されます。これは、式の表示においてidealboolを特別に扱う必要があることを示しています。
  4. テストケースの変更:

    • test/fixedbugs/bug285.go: mb[false] = 42という行が追加されます。このテストは、falseという型なし真偽値リテラルが、type B boolで定義された名前付き型Bのマップのキーとして、明示的な変換なしに代入できることを検証しています。これは、「ideal bool」の導入によって可能になった柔軟な型付けの例です。
    • test/named.gotest/named1.go: Bool = Bool(true)のような明示的な型変換を伴う代入が、Bool = trueのような直接的な代入に置き換えられ、以前はエラーとされていたasBool(true)のような呼び出しが許可されるようになります。これは、型なし真偽値リテラルが、名前付きの真偽値型に直接代入可能になったことを示しています。

これらの変更により、Goコンパイラは真偽値リテラルをより柔軟に扱い、開発者が明示的な型変換を記述することなく、直感的にコードを書けるようになります。これは、Go言語の型システムの使いやすさを向上させる重要な改善です。

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

このコミットにおけるコアとなるコードの変更箇所は、主にGoコンパイラの型システムと字句解析、定数評価に関連するファイルに集中しています。

  1. src/cmd/gc/go.h:

    --- a/src/cmd/gc/go.h
    +++ b/src/cmd/gc/go.h
    @@ -775,6 +775,7 @@ EXTERN	Idir*\tidirs;
     
     EXTERN	Type*\ttypes[NTYPE];
     EXTERN	Type*\tidealstring;
    +EXTERN	Type*\tidealbool;
     EXTERN	Type*\tbytetype;
     EXTERN	Type*\trunetype;
     EXTERN	Type*\terrortype;
    

    idealboolという新しいグローバルなTypeポインタが宣言されています。これは、Goコンパイラ全体で「理想的な真偽値型」を表すために使用されます。

  2. src/cmd/gc/lex.c:

    --- a/src/cmd/gc/lex.c
    +++ b/src/cmd/gc/lex.c
    @@ -1824,16 +1824,17 @@ lexinit(void)\n \t// this is the ideal form\n \t// (the type of x in const x = \"hello\").\n \tidealstring = typ(TSTRING);\n    +\tidealbool = typ(TBOOL);\n     \n     \ts = pkglookup(\"true\", builtinpkg);\n     \ts->def = nodbool(1);\n     \ts->def->sym = lookup(\"true\");\n    -\ts->def->type = types[TBOOL];\n    +\ts->def->type = idealbool;\n     \n     \ts = pkglookup(\"false\", builtinpkg);\n     \ts->def = nodbool(0);\n     \ts->def->sym = lookup(\"false\");\n    -\ts->def->type = types[TBOOL];\n    +\ts->def->type = idealbool;\n     \n     \ts = lookup(\"_\");\n     \ts->block = -100;\n    ```
    `lexinit`関数内で`idealbool`が`TBOOL`を基底として初期化され、さらに組み込みの`true`と`false`リテラルの型が`types[TBOOL]`から`idealbool`に変更されています。これにより、真偽値リテラルは字句解析の段階で型なしとして扱われるようになります。
    
    
  3. src/cmd/gc/const.c:

    --- a/src/cmd/gc/const.c
    +++ b/src/cmd/gc/const.c
    @@ -943,7 +944,7 @@ nodlit(Val v)\n     \t\tn->type = idealstring;\n     \t\tbreak;\n     \tcase CTBOOL:\n    -\t\tn->type = types[TBOOL];\n    +\t\tn->type = idealbool;\n     \t\tbreak;\n     \tcase CTINT:\n     \tcase CTRUNE:\n    @@ -1032,7 +1033,7 @@ defaultlit(Node **np, Type *t)\n     \t\t\tdefaultlit(&n->left, t);\n     \t\t\tdefaultlit(&n->right, t);\n     \t\t}\n    -\t\tif(n->type == types[TBOOL] || n->type == idealstring)\n    +\t\tif(n->type == idealbool || n->type == idealstring)\n     \t\t\tn->type = types[n->type->etype];\n     \t\telse\n     \t\t\tn->type = n->left->type;\n    ```
    `nodlit`関数で真偽値定数(`CTBOOL`)のノードの型が`idealbool`に設定され、`defaultlit`関数で型なし真偽値のチェックが`idealbool`に対して行われるよう変更されています。
    
    
  4. src/cmd/gc/subr.c:

    --- a/src/cmd/gc/subr.c
    +++ b/src/cmd/gc/subr.c
    @@ -768,7 +768,7 @@ nodbool(int b)\n     \tc = nodintconst(0);\n     \tc->val.ctype = CTBOOL;\n     \tc->val.u.bval = b;\n    -\tc->type = types[TBOOL];\n    +\tc->type = idealbool;\n     \treturn c;\n     }\n     \n    @@ -929,7 +929,7 @@ isideal(Type *t)\n     {\n     \tif(t == T)\n     \t\treturn 0;\n    -\tif(t == idealstring)\n    +\tif(t == idealstring || t == idealbool)\n     \t\treturn 1;\n     \tswitch(t->etype) {\n     \tcase TNIL:\n    ```
    `nodbool`関数で真偽値ノードの型が`idealbool`に設定され、`isideal`関数で`idealbool`が「理想的な型」として認識されるよう変更されています。
    
    

これらの変更が、Go言語の真偽値リテラルの型推論と型チェックの挙動に直接影響を与えます。

コアとなるコードの解説

このコミットの核心は、Go言語のコンパイラが真偽値リテラル(truefalse)をどのように扱うかという点にあります。以前は、これらのリテラルはすぐに具象的なbool型(コンパイラ内部ではtypes[TBOOL]で表現される)に割り当てられていました。しかし、このコミットによって、真偽値リテラルは「ideal bool」という「理想的な型」(または「型なし型」)を持つようになります。

idealboolの導入と初期化 (src/cmd/gc/go.h, src/cmd/gc/lex.c): src/cmd/gc/go.hEXTERN Type* idealbool;として宣言されるidealboolは、Goコンパイラ全体で共有される特別な型オブジェクトへのポインタです。 src/cmd/gc/lex.clexinit関数内でidealbool = typ(TBOOL);と初期化されます。これは、idealboolが内部的にはTBOOL(組み込みのbool型)を基底とする型であることを示していますが、その振る舞いは通常のTBOOLとは異なります。idealboolは、数値リテラルが「理想的な整数型」や「理想的な浮動小数点型」を持つように、真偽値リテラルのための型なしのプレースホルダーとして機能します。

真偽値リテラルの型付け変更 (src/cmd/gc/lex.c, src/cmd/gc/const.c, src/cmd/gc/subr.c): 最も重要な変更は、src/cmd/gc/lex.clexinit関数で、組み込みのtruefalseシンボルの型がtypes[TBOOL]からidealboolに変更された点です。これにより、ソースコード中のtruefalseは、字句解析の段階でidealbool型を持つノードとしてコンパイラに渡されます。 src/cmd/gc/const.cnodlit関数は、リテラルノードを作成する際に、真偽値定数(CTBOOL)に対してidealbool型を割り当てます。また、defaultlit関数は、型なしリテラルにデフォルトの型を割り当てる際に、idealboolを特別に扱います。 src/cmd/gc/subr.cnodbool関数も、真偽値ノードを作成する際にidealbool型を使用するようになります。 これらの変更により、真偽値リテラルは、それが使用される文脈(例えば、変数への代入や関数の引数)に応じて、適切な具象的なbool型(または基底型がboolである名前付き型)に自動的に変換されるようになります。

「理想的な型」としての認識 (src/cmd/gc/subr.c): src/cmd/gc/subr.cisideal関数は、与えられた型が「理想的な型」(型なし定数の型)であるかどうかを判定します。このコミットでは、t == idealboolという条件が追加され、idealboolidealstring(文字列リテラルの理想的な型)と同様に、型なし定数として認識されるようになります。これにより、コンパイラの型チェックロジックがidealboolを適切に処理できるようになります。

型チェックとエクスポート、フォーマットへの影響 (src/cmd/gc/export.c, src/cmd/gc/fmt.c): src/cmd/gc/export.creexportdep関数では、型情報をエクスポートする際にidealboolを考慮するよう変更されます。これは、コンパイルされたパッケージが他のパッケージから利用される際に、型なし真偽値のセマンティクスが正しく伝わるようにするためです。 src/cmd/gc/fmt.ctypefmtexprfmt関数では、デバッグ出力やエラーメッセージにおいてidealboolが「ideal bool」として表示されるようになります。これは、コンパイラの開発者やデバッグ時に、型の状態をより明確に把握するのに役立ちます。

テストケースによる検証 (test/fixedbugs/bug285.go, test/named.go, test/named1.go): 追加および変更されたテストケースは、この「ideal bool」の導入によって可能になった、より柔軟な型付けの挙動を検証しています。例えば、mb[false] = 42というコードは、falseという型なし真偽値リテラルが、type B boolで定義された名前付き型Bのマップのキーとして、明示的な型変換なしに利用できることを示しています。これは、以前はコンパイルエラーとなっていた可能性のあるコードが、この変更によって正しく動作するようになることを意味します。

総じて、このコミットはGo言語の型システムをより一貫性のあるものにし、真偽値リテラルを数値や文字列リテラルと同様に、型なし定数として扱うことで、開発者のコーディング体験を向上させています。

関連リンク

  • Go言語の型システムに関する公式ドキュメント: Go言語の型システム、特に型なし定数に関する詳細な情報は、Go言語の仕様書や公式ブログで確認できます。
  • Go言語のコンパイラソースコード:
  • Gerrit Code Review: Goプロジェクトがコードレビューに利用しているシステム。CL番号(Change List)はGerrit上の変更を指します。

参考にした情報源リンク

  • Go言語の公式ドキュメントと仕様書: Go言語の型システム、特に定数と型推論に関する公式な定義は、Go言語の仕様書に記載されています。
  • Go言語のソースコード: 実際のコンパイラの挙動を理解するために、src/cmd/gc/以下の関連ファイル(const.c, export.c, fmt.c, go.h, lex.c, subr.c)の変更点を詳細に分析しました。
  • Go言語のテストケース: test/fixedbugs/bug285.go, test/named.go, test/named1.goの変更は、このコミットがもたらす具体的な挙動の変化を理解する上で非常に重要でした。
  • Go言語のコミュニティと議論: Go言語のメーリングリスト(golang-devなど)やIssueトラッカーでの議論は、特定の変更の背景や意図を理解する上で役立つことがあります。今回のケースでは、コミットメッセージに「last weekly」や「less strict spec」といった言及があり、これはコミュニティ内での議論があったことを示唆しています。
  • Go言語に関する技術ブログや記事: 「Go ideal bool」や「Go untyped boolean」といったキーワードで検索することで、Go言語の型システムにおける真偽値の扱いや、型なし定数に関する一般的な解説記事を参照しました。