[インデックス 15574] ファイルの概要
このコミットは、Goコンパイラ(cmd/gc
)におけるリテラル(定数)の型推論とデフォルト型変換を扱うdefaultlit
関数の簡素化と修正を目的としています。特に、理想型(ideal type、Goにおける型付けされていない定数や式)の扱いを改善し、シフト演算子を含む式における型エラーの検出を強化しています。
コミット
commit a85fce282e779cb8e342b5efafcd2e11eea3f9ce
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Mon Mar 4 16:51:42 2013 +0100
cmd/gc: simplify and fix defaultlit.
Fixes #4882.
Fixes #4936.
Fixes #4937.
R=golang-dev, dave, daniel.morsing, rsc
CC=golang-dev
https://golang.org/cl/7432044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a85fce282e779cb8e342b5efafcd2e11eea3f9ce
元コミット内容
cmd/gc: simplify and fix defaultlit.
このコミットは、Goコンパイラのcmd/gc
パッケージ内のdefaultlit
関数を簡素化し、修正します。
具体的には、以下の問題に対応しています。
- Fixes #4882.
- Fixes #4936.
- Fixes #4937.
これらの問題は、Goの古い内部イシュートラッカー、またはGoプロジェクトとは異なる外部プロジェクトのイシューを参照している可能性があり、現在のgolang/go
リポジトリの公開イシュートラッカーでは直接参照できません。しかし、コードの変更内容から、リテラルと型推論、特にシフト演算子を含む式における型変換の挙動に関するバグ修正と改善であることが示唆されます。
変更の背景
Go言語では、数値リテラル(例: 100
、3.14
)は、それが使用される文脈によって型が決定される「型付けされていない定数(untyped constants)」として扱われます。これにより、柔軟な数値演算が可能になりますが、特定の状況下では予期せぬ型推論やオーバーフロー、無効な操作が発生する可能性があります。
特に、シフト演算子(<<
, >>
)は整数型に対してのみ有効であり、浮動小数点数などの非整数型をシフトしようとするとコンパイルエラーとなるべきです。しかし、型付けされていない定数と他の型の組み合わせによっては、コンパイラが適切なエラーを検出できない、または誤った型推論を行うバグが存在したと考えられます。
このコミットは、defaultlit
関数(リテラルのデフォルト型を決定する役割を持つ)のロジックを改善し、これらの問題を解決することを目的としています。特に、シフト演算子を含む「理想型」の式が正しく評価され、適切な型チェックが行われるように修正が加えられています。
前提知識の解説
Go言語の型システムとリテラル
Go言語の型システムには、以下のような特徴があります。
- 型付けされた型 (Typed Types):
int
,float64
,string
,bool
など、明確に型が定義された値。 - 型付けされていない定数 (Untyped Constants): 数値リテラル(例:
100
,3.14
,1i
)、真偽値リテラル(true
,false
)、文字列リテラル("hello"
)は、デフォルトでは型付けされていません。これらは「理想型(ideal type)」とも呼ばれ、その値が表現できる範囲内で、より大きな精度を持つことができます。untyped int
(理想整数)untyped float
(理想浮動小数点数)untyped complex
(理想複素数)untyped rune
(理想ルーン)untyped bool
(理想真偽値)untyped string
(理想文字列) これらの型付けされていない定数は、変数に代入されたり、型付けされた値と演算されたりする際に、文脈に応じて適切な型に変換されます。
Goコンパイラの構造 (cmd/gc
)
cmd/gc
は、Go言語の公式コンパイラの一部であり、Goソースコードを機械語に変換するプロセスの中核を担っています。コンパイルプロセスは、主に以下のフェーズに分かれます。
- 字句解析 (Lexing): ソースコードをトークンに分割します。
- 構文解析 (Parsing): トークン列から抽象構文木 (AST: Abstract Syntax Tree) を構築します。
- 型チェックと意味解析 (Type Checking and Semantic Analysis): ASTを走査し、型の整合性をチェックし、意味的なエラー(例: 未定義変数、型不一致)を検出します。このフェーズで、型付けされていない定数に具体的な型が割り当てられる(デフォルト型変換)ことがあります。
- 中間コード生成 (Intermediate Code Generation): ASTをより低レベルの中間表現に変換します。
- 最適化 (Optimization): 中間コードを最適化します。
- コード生成 (Code Generation): 最終的な機械語コードを生成します。
defaultlit
関数は、主に型チェックと意味解析のフェーズで動作し、型付けされていないリテラルや式にデフォルトの型を割り当てる役割を担っています。
シフト演算子と型
Go言語において、シフト演算子(<<
, >>
)の左オペランドは整数型である必要があります。浮動小数点数や複素数などの非整数型をシフトしようとすると、コンパイルエラーとなります。型付けされていない定数がシフト演算子の左オペランドになる場合、その定数はまず整数型に変換される必要があります。この変換が適切に行われないと、誤った型が推論されたり、無効なシフト操作が許容されたりする問題が発生します。
技術的詳細
このコミットの主要な変更点は、src/cmd/gc/const.c
ファイルにおけるdefaultlit
関数のロジックの再構築です。
idealkind
関数の導入
新しい静的関数idealkind(Node *n)
が導入されました。この関数は、与えられたASTノードn
が表す「理想型」の式の種類(CTINT
, CTBOOL
, CTSTR
など)を再帰的に判断します。
OLITERAL
(リテラル)の場合、ノード自身の定数型(n->val.ctype
)を返します。OADD
,OMUL
,OSUB
などの数値演算の場合、左右のオペランドのidealkind
を再帰的に呼び出し、より「大きい」種類(例: 整数と浮動小数点数なら浮動小数点数)を返します。OADDSTR
(文字列結合)の場合、CTSTR
を返します。OANDAND
,OOROR
,OEQ
などの論理/比較演算の場合、CTBOOL
を返します。OLSH
,ORSH
(シフト)の場合、左オペランドのidealkind
を返します。これは、シフト演算の結果の型が左オペランドの型に依存するためです。
このidealkind
関数は、defaultlit
関数がリテラルや理想型の式のデフォルト型を決定する際の、より堅牢な基盤を提供します。
defaultlit
関数の簡素化と修正
以前のdefaultlit
関数は、switch(n->op)
を使ってノードの操作(OLITERAL
, OLSH
, OCOM
など)に基づいて複雑な分岐ロジックを持っていました。特に、シフト演算子を含む式や、左右のオペランドが異なる種類の理想型である場合の処理が複雑で、バグの原因となっていました。
新しいdefaultlit
関数は、この複雑な分岐を大幅に簡素化しています。
- リテラルノードのコピー:
n->op == OLITERAL
の場合、ノードを直接変更するのではなく、新しいノードnn
を作成して元のリテラルをコピーし、*np
をnn
に更新します。これにより、元のASTノードの不変性を保ちつつ、型情報を追加できるようになります。 idealkind
の活用:defaultlit
の主要なロジックは、新しく導入されたidealkind(n)
の結果に基づいてswitch(ctype)
で分岐するようになりました。これにより、式の種類に応じたデフォルト型変換がより明確かつ一貫して行われます。convlit
の利用: 各ケース(CTBOOL
,CTINT
,CTFLT
など)で、適切なデフォルト型t1
を決定した後、convlit(np, t1)
を呼び出して実際の型変換を行います。これにより、型変換ロジックが一元化され、重複が排除されます。- シフト演算子の特殊処理の削除: 以前のバージョンでは、シフト演算子(
OLSH
,ORSH
)に対して特別なdefaultlit
の再帰呼び出しロジックがありましたが、これは削除されました。idealkind
がシフト演算子の左オペランドの型を適切に返すようになったため、defaultlit
はより一般的な方法で処理できるようになりました。これにより、1.0 << s
のような無効なシフト操作が、より早期かつ正確に「invalid operation|shift of non-integer operand」として検出されるようになります。 - オーバーフローチェックの改善:
overflow(n->val, t1)
のように、決定されたデフォルト型t1
に基づいてオーバーフローチェックが行われるようになりました。これにより、型変換後の値がその型で表現できる範囲内にあるかどうかが正確に検証されます。
これらの変更により、defaultlit
関数はよりモジュール化され、理解しやすくなり、Go言語の型付けされていない定数と演算に関する既存のバグが修正されました。
コアとなるコードの変更箇所
src/cmd/gc/const.c
idealkind
関数の新規追加。defaultlit
関数の大幅な変更。switch(n->op)
による複雑な分岐ロジックを削除。idealkind(n)
の結果に基づくswitch(ctype)
による新しい分岐ロジックを導入。- 各ケースで
convlit(np, t1)
を呼び出すように変更し、型変換ロジックを一元化。 - シフト演算子に関する特殊な再帰呼び出しロジックを削除。
- オーバーフローチェックの引数を
n->type
からt1
(決定されたデフォルト型)に変更。
test/shift1.go
- 新しいテストケースの追加。
a3 = 1.0<<s + 0
b3 = 1<<s + 1 + 1.0
これらのテストケースは、浮動小数点数を含むシフト演算が正しく「invalid operation|shift of non-integer operand」エラーとして検出されることを検証しています。これは、このコミットが修正しようとしている具体的な問題を示しています。
コアとなるコードの解説
idealkind
関数
static int
idealkind(Node *n)
{
int k1, k2;
if(n == N || !isideal(n->type))
return CTxxx; // 理想型でない場合は不明な型
switch(n->op) {
default:
return CTxxx; // デフォルトでは不明な型
case OLITERAL:
return n->val.ctype; // リテラルの場合はその定数型
case OADD: // 数値演算
case OAND:
// ... (他の数値演算子)
case OPLUS:
k1 = idealkind(n->left);
k2 = idealkind(n->right);
if(k1 > k2) // より「大きい」種類(例: float > int)を返す
return k1;
else
return k2;
case OADDSTR:
return CTSTR; // 文字列結合は文字列型
case OANDAND: // 論理/比較演算
case OEQ:
// ... (他の論理/比較演算子)
case OCMPIFACE:
return CTBOOL; // 論理/比較演算は真偽値型
case OLSH: // シフト演算
case ORSH:
return idealkind(n->left); // 左オペランドの理想型を返す
}
}
この関数は、ASTノードが表す式の「理想型」の種類を決定します。特に、シフト演算子の場合、左オペランドの理想型が結果の型に影響を与えるというGoのセマンティクスを反映しています。これにより、defaultlit
がより正確な型推論を行うための情報を提供します。
defaultlit
関数
void
defaultlit(Node **np, Type *t)
{
int lno;
int ctype; // idealkindの結果を格納
Node *n, *nn;
Type *t1; // 最終的に変換される型
n = *np;
if(n == N || !isideal(n->type))
return; // ノードが存在しないか、理想型でない場合は何もしない
if(n->op == OLITERAL) {
nn = nod(OXXX, N, N); // 新しいノードを作成
*nn = *n; // 元のノードの内容をコピー
n = nn;
*np = n; // ポインタを新しいノードに更新
}
lno = setlineno(n); // 行番号を保存
ctype = idealkind(n); // ノードの理想型種類を取得
t1 = T; // 変換先の型を初期化 (Tはnil型のようなもの)
switch(ctype) {
default:
if(t != T) { // ターゲット型が指定されている場合
convlit(np, t); // その型に変換
return;
}
// ... (CTNIL, CTSTRなどの特殊処理)
yyerror("defaultlit: unknown literal: %N", n);
break;
case CTxxx:
fatal("defaultlit: idealkind is CTxxx: %+N", n); // idealkindがCTxxxを返したら致命的エラー
break;
case CTBOOL:
t1 = types[TBOOL]; // デフォルトはbool型
if(t != T && t->etype == TBOOL)
t1 = t; // ターゲット型がboolならそれに従う
convlit(np, t1); // 変換
break;
case CTINT:
t1 = types[TINT]; // デフォルトはint型
goto num; // 数値型共通の処理へ
case CTRUNE:
t1 = runetype; // デフォルトはrune型
goto num;
case CTFLT:
t1 = types[TFLOAT64]; // デフォルトはfloat64型
goto num;
case CTCPLX:
t1 = types[TCOMPLEX128]; // デフォルトはcomplex128型
goto num;
num: // 数値型共通の処理
if(t != T) { // ターゲット型が指定されている場合
if(isint[t->etype]) {
t1 = t;
n->val = toint(n->val); // 整数に変換
} else if(isfloat[t->etype]) {
t1 = t;
n->val = toflt(n->val); // 浮動小数点数に変換
} else if(iscomplex[t->etype]) {
t1 = t;
n->val = tocplx(n->val); // 複素数に変換
}
}
overflow(n->val, t1); // オーバーフローチェック
convlit(np, t1); // 変換
break;
}
lineno = lno; // 行番号を元に戻す
}
この改訂されたdefaultlit
関数は、idealkind
によって得られた式の種類に基づいて、より構造化された方法でデフォルト型を決定し、convlit
関数を通じて実際の型変換を実行します。これにより、以前の複雑でエラーを起こしやすい分岐ロジックが排除され、特にシフト演算子を含む式における型推論の正確性が向上しました。
関連リンク
- Go言語の型システムに関する公式ドキュメント(Go 1.0当時の情報を見つけるのは難しいですが、現在のドキュメントでも基本的な概念は共通しています):
- Goコンパイラのソースコード(
src/cmd/gc
ディレクトリ):
参考にした情報源リンク
- コミットメッセージに記載されているGoのコードレビューシステムへのリンク:
- https://golang.org/cl/7432044 (このリンクは現在、GoのGerritコードレビューシステムにリダイレクトされます。当時のレビュー内容を確認できます。)
- Go言語のコンパイラに関する一般的な情報源:
- "Go Programming Language" by Alan A. A. Donovan and Brian W. Kernighan (Go言語の設計思想やコンパイラの基本的な概念を理解するのに役立ちます)
- Go言語の公式ブログや設計ドキュメント(Goの進化の歴史を辿る上で有用です)
- Goのイシュートラッカー(現在のもの):
- golang/go issues on GitHub (ただし、このコミットで参照されている古いイシュー番号は、現在のトラッカーでは見つかりませんでした。)