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

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

このコミットは、Goコンパイラの初期段階における定数処理に関連する src/cmd/gc/const.c ファイルに対する変更です。具体的には、convlit 関数内のリテラル型変換ロジックが修正されています。

コミット

commit 504aa698f7790711a191f78c63ada53d6e08e8cd
Author: Ken Thompson <ken@golang.org>
Date:   Tue Jun 17 18:01:05 2008 -0700

    SVN=123249
---
 src/cmd/gc/const.c | 14 +++++++++-----\n 1 file changed, 9 insertions(+), 5 deletions(-)\n

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

https://github.com/golang/go/commit/504aa698f7790711a191f78c63ada53d6e08e8cd

元コミット内容

SVN=123249

このコミットメッセージは非常に簡潔で、当時のGoプロジェクトがSubversion (SVN) をバージョン管理システムとして使用していたことを示唆しています。SVN=123249 は、このコミットがSubversionリポジトリの123249番目のリビジョンに対応することを示しています。これは、GoプロジェクトがGitに移行する前の初期のコミットによく見られる形式です。

変更の背景

このコミットは、Goコンパイラの初期開発段階における型システムと定数処理の改善の一環として行われました。Go言語では、リテラル(数値、文字列、nilなど)は初期段階では「型なし(untyped)」として扱われ、その使用される文脈によって具体的な型に変換されます。この変換プロセスはコンパイラの重要な部分であり、特にnilのような特殊なリテラルは、ポインタ、スライス、マップ、インターフェースなど、複数の型に適合する可能性があるため、その型推論と変換は複雑です。

このコミットの変更は、convlit関数におけるリテラルの型変換ロジックをより堅牢にし、特にnilリテラルがポインタ型やインターフェース型にのみ変換可能であることを明示的に強制するためのものです。これにより、コンパイラが不正な型変換を早期に検出し、より正確な型チェックを行うことが可能になります。

前提知識の解説

このコミットを理解するためには、以下のGoコンパイラの内部概念とGo言語の基本的な型システムに関する知識が必要です。

  • Goコンパイラの構造 (初期):
    • src/cmd/gc: これはGoコンパイラの初期の主要なディレクトリでした。現在のGoコンパイラは src/cmd/compile/internal/gc に再編成されていますが、基本的な役割は同じです。gc は "Go Compiler" の略です。
    • const.c: このファイルは、コンパイラが定数(リテラル)をどのように扱うか、特にそれらの型変換に関するロジックを含んでいました。C言語で書かれているのは、Goコンパイラの初期バージョンがC言語で実装されていたためです。
  • NodeOLITERAL:
    • Goコンパイラは、ソースコードを解析して抽象構文木 (AST: Abstract Syntax Tree) を構築します。ASTの各要素は Node として表現されます。
    • OLITERAL は、ASTノードの一種で、ソースコード中のリテラル値(例: 10, "hello", true, nil)を表します。
  • Typeetype:
    • Goコンパイラは、プログラム内のすべての式や変数に型を割り当てます。Type はこれらの型を表す内部構造体です。
    • etype (element type) は、Type 構造体内のフィールドで、その型の基本的な種類(例: TINT (整数), TBOOL (真偽値), TSTRING (文字列), TPTR (ポインタ), TINTER (インターフェース) など)を示します。
  • 型なし定数 (Untyped Constants):
    • Go言語の数値、真偽値、文字列リテラルは、デフォルトでは「型なし」です。例えば、10 は単なる数値であり、intint32float64 など、特定の型を持っていません。
    • これらの型なし定数は、変数への代入や式の中で使用される際に、その文脈に基づいて適切な型に変換されます。
  • nil リテラル:
    • nil はGoにおける特殊なキーワードで、ポインタ、スライス、マップ、インターフェース、チャネル、関数のゼロ値を表します。
    • nil もまた型なしリテラルですが、他の型なし定数とは異なり、特定のカテゴリの型(ポインタ、インターフェースなど)にのみ変換可能です。整数や文字列にnilを代入することはできません。
  • convlit 関数:
    • この関数は、リテラルノード n を特定のターゲット型 t に変換しようと試みます。コンパイラの型チェックフェーズで重要な役割を果たします。
  • whatis(n):
    • これは、与えられたノード n がどのような種類のリテラルであるかを識別するための内部関数またはマクロです。
    • Wlitnil: nilリテラルを表します。
    • Wlitint: 整数リテラルを表します。
  • isptrto(t, TANY):
    • tTANY へのポインタ型であるかどうかをチェックする関数です。初期のGoコンパイラでは、TANY は任意の型を表すプレースホルダーとして使用されていた可能性があります。
  • defaultlit(n):
    • リテラル n にデフォルトの型を割り当てる関数です。例えば、型が明示されていない整数リテラルには int 型が割り当てられる、といった処理を行います。
  • isptr[et]:
    • et がポインタ型であるかどうかをチェックするための配列またはマクロです。

技術的詳細

このコミットの主要な変更点は、convlit 関数におけるリテラル型変換の厳密化です。

変更前のコードでは、t->etype == TANY || isptrto(t, TANY) の条件で、ターゲット型が TANY であるか、TANY へのポインタである場合に defaultlit(n) を呼び出していましたが、このブロックが削除されました。これは、より具体的な型チェックロジックを導入するためと考えられます。

変更後のコードでは、switch(whatis(n)) ステートメントに default ケースと case Wlitnil が追加されています。

  1. default:goto bad1;:
    • これは、WlitnilWlitint などの既知のリテラルタイプ以外の未知のリテラルタイプが convlit に渡された場合に、エラー処理ラベル bad1 にジャンプすることを示しています。これにより、コンパイラは予期しないリテラルタイプを適切に処理できるようになります。
  2. case Wlitnil: の追加:
    • このケースは、ノード nnil リテラルである場合に実行されます。
    • if(isptr[et] || et == TINTER): この条件は、ターゲット型 tetype がポインタ型 (isptr[et]) であるか、またはインターフェース型 (et == TINTER) であるかをチェックします。
    • break;: もし条件が真(つまり、nilがポインタ型またはインターフェース型に変換される場合)であれば、switch文を抜けて処理を続行します。これは、nilがこれらの型にのみ有効に変換できるというGo言語のセマンティクスを反映しています。
    • goto bad1;: もし条件が偽(つまり、nilがポインタ型でもインターフェース型でもない型に変換されようとしている場合)であれば、エラー処理ラベル bad1 にジャンプします。これにより、例えば var i int = nil のような不正なコードがコンパイル時に検出されるようになります。

この変更により、Goコンパイラはnilリテラルの型変換に関してより厳密なルールを適用し、コンパイル時のエラー検出能力を向上させています。これは、Go言語の型安全性を保証する上で重要なステップです。

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

--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -9,17 +9,21 @@ void
 convlit(Node *n, Type *t)
 {
  int et;
+ Node *n1;
 
  if(n == N || n->op != OLITERAL || t == T)
  return;
 
- if(t->etype == TANY || isptrto(t, TANY)) {
-  defaultlit(n);
-  return;
- }
-
  et = t->etype;
  switch(whatis(n)) {
+ default:
+  goto bad1;
+
+ case Wlitnil:
+  if(isptr[et] || et = TINTER)
+   break;
+  goto bad1;
+
  case Wlitint:
  if(isptrto(t, TSTRING)) {
   Rune rune;

コアとなるコードの解説

  • if(t->etype == TANY || isptrto(t, TANY)) ブロックの削除:
    • 変更前は、ターゲット型が TANY (任意の型) であるか、TANY へのポインタである場合に、リテラルにデフォルトの型を割り当てていました。この汎用的な処理を削除し、より具体的な型チェックロジックに置き換えることで、コンパイラの型推論と変換の精度を高めています。
  • switch(whatis(n)) 内の default: ケースの追加:
    • default: goto bad1;
    • これは、whatis(n) が返すリテラルタイプが、switch 文で明示的に処理されていない未知のタイプである場合に、エラー処理ルーチン bad1 へジャンプすることを示します。これにより、コンパイラは未定義のリテラルタイプに対して堅牢になります。
  • case Wlitnil: の追加とロジック:
    • case Wlitnil:
    • if(isptr[et] || et == TINTER)
    • break;
    • goto bad1;
    • このブロックは、Go言語の nil リテラルのセマンティクスを厳密に強制します。nil はポインタ型 (isptr[et]) またはインターフェース型 (et == TINTER) にのみ変換可能です。この条件が満たされれば breakswitch を抜け、そうでなければ goto bad1 でエラーとします。これにより、nilを不適切な型(例: 整数、文字列)に代入しようとした場合にコンパイルエラーが発生するようになります。

これらの変更は、Goコンパイラがリテラル、特にnilリテラルの型変換をより正確かつ厳密に行うための基盤を強化し、Go言語の型安全性を初期段階から確立する上で重要な役割を果たしています。

関連リンク

参考にした情報源リンク