[インデックス 10037] ファイルの概要
コミット
コミットハッシュ: 862179b0f58a0f245a820be6c767a7e8ec0f6e88
作成者: Russ Cox rsc@golang.org
日付: 2011年10月18日 14:55:50 -0400
タイトル: gc: preserve uint8 and byte distinction in errors, import data
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/862179b0f58a0f245a820be6c767a7e8ec0f6e88
元コミット内容
このコミットの内容は以下の通りです:
gc: preserve uint8 and byte distinction in errors, import data
There is no semantic change here, just better errors.
If a function says it takes a byte, and you pass it an int,
the compiler error now says that you need a byte, not
that you need a uint8.
Groundwork for rune.
このコミットは、以下の8つのファイルを変更しています:
src/cmd/gc/builtin.c.boot
(84行の変更)src/cmd/gc/export.c
(2行の変更)src/cmd/gc/go.h
(1行の追加)src/cmd/gc/lex.c
(22行の追加)src/cmd/gc/reflect.c
(2行の変更)src/cmd/gc/subr.c
(37行の変更)src/cmd/gc/typecheck.c
(4行の変更)test/alias.go
(19行の新規ファイル)
変更の背景
このコミットは、Goコンパイラにおけるエラーメッセージの改善を目的として行われました。具体的には、uint8
とbyte
の区別をエラーメッセージ内で保持することで、より分かりやすいエラー表示を実現しています。
問題の発生理由
従来のGoコンパイラでは、byte
はuint8
の別名(alias)として定義されていましたが、エラーメッセージ生成時に内部的にuint8
に変換されてしまい、ユーザーがソースコードでbyte
を使用していてもエラーメッセージではuint8
が表示されていました。これは、特に初心者プログラマーにとって混乱の原因となっていました。
改善の必要性
- ユーザー体験の向上: 開発者がソースコードで
byte
を使用している場合、エラーメッセージでもbyte
と表示されるべき - 可読性の向上: エラーメッセージがソースコードの記述と一致することで、デバッグが容易になる
- 将来の拡張への準備: コミットメッセージにある「Groundwork for rune」という記述から、将来的な
rune
型の導入に向けた基盤作り
前提知識の解説
Goの型システムにおけるエイリアス
Go言語では、byte
はuint8
の別名(type alias)として定義されています。現在のGo言語仕様では以下のように定義されています:
type byte = uint8
type rune = int32
コンパイラのアーキテクチャ(2011年時点)
2011年当時のGoコンパイラは、現在とは異なるアーキテクチャを採用していました:
- gcコンパイラ: C言語で書かれたコンパイラ(現在はGo言語で書き直されている)
- ブートストラップファイル:
builtin.c.boot
ファイルにランタイム関数のインポート情報が記述されていた - 型システム: 内部的に型を表現するためのデータ構造が存在
Unicode文字とrune型の関係
このコミットが「Groundwork for rune」と述べているのは、Go言語のUnicode文字処理の基盤を整備するためです。rune
型は、Unicode code pointを表現するためにint32
の別名として後に導入されました。
技術的詳細
新しいbytetype変数の導入
src/cmd/gc/go.h
に新しいグローバル変数bytetype
が追加されました:
EXTERN Type* bytetype;
この変数は、byte
型の専用の型情報を保持し、uint8
型とは区別して管理するために使用されます。
字句解析器の拡張
src/cmd/gc/lex.c
では、lexinit1()
関数が追加され、byte
型の特別な初期化処理が行われます:
static void
lexinit1(void)
{
Sym *s, *s1;
// byte alias
s = lookup("byte");
s->lexical = LNAME;
bytetype = typ(TUINT8);
bytetype->sym = s;
s1 = pkglookup("byte", builtinpkg);
s1->lexical = LNAME;
s1->def = typenod(bytetype);
}
型等価性判定の改善
src/cmd/gc/subr.c
のeqtype()
関数に特別な処理が追加されました:
if(t1->sym || t2->sym) {
// Special case: we keep byte and uint8 separate
// for error messages. Treat them as equal.
switch(t1->etype) {
case TUINT8:
if((t1 == types[TUINT8] || t1 == bytetype) && (t2 == types[TUINT8] || t2 == bytetype))
return 1;
break;
}
return 0;
}
この変更により、byte
とuint8
は意味的には等価でありながら、エラーメッセージでは区別されるようになりました。
コアとなるコードの変更箇所
1. builtin.c.boot の変更
ランタイム関数のインポート情報で、*uint8
パラメータを*byte
に変更:
// Before
"func @\"\".new (typ *uint8) *any\n"
// After
"func @\"\".new (typ *byte) *any\n"
2. 型表示関数の改善
Tpretty()
関数にbytetype
の特別な処理を追加:
if(noargnames) {
// called from typesym
if(t == bytetype)
t = types[bytetype->etype];
}
3. 型変換処理の改善
convertop()
関数で、eqtype()
を使用した型比較に変更:
// Before
if(isslice(src) && src->sym == nil && src->type == types[src->type->etype] && dst->etype == TSTRING) {
switch(src->type->etype) {
case TUINT8:
return OARRAYBYTESTR;
}
}
// After
if(isslice(src) && src->sym == nil && dst->etype == TSTRING) {
if(eqtype(src->type, bytetype))
return OARRAYBYTESTR;
}
コアとなるコードの解説
型システムの拡張メカニズム
このコミットの核心は、Go言語の型システムに「意味的等価性」と「表示上の区別」を同時に実現する仕組みを導入したことです。
1. 型情報の二重管理
// 既存のuint8型
Type* types[TUINT8];
// 新しいbyte型(内部的にはuint8だが、シンボル情報が異なる)
Type* bytetype;
bytetype
はtyp(TUINT8)
で作成されるため、内部的にはuint8
と同じ構造を持ちますが、シンボル情報(sym
フィールド)が異なります。
2. 型等価性判定の階層化
eqtype(Type *t1, Type *t2)
{
if(t1 == t2)
return 1; // 完全に同じ型
if(t1 == T || t2 == T || t1->etype != t2->etype)
return 0; // 基本型が異なる場合は不等価
if(t1->sym || t2->sym) {
// 名前付き型の場合の特別処理
switch(t1->etype) {
case TUINT8:
if((t1 == types[TUINT8] || t1 == bytetype) &&
(t2 == types[TUINT8] || t2 == bytetype))
return 1; // byte と uint8 は等価
break;
}
return 0;
}
// 構造的等価性の判定が続く...
}
3. エラーメッセージ生成の改善
Tpretty()
関数では、型を文字列として表示する際に、元のソースコードで使用された型名を保持します:
if(t->etype != TFIELD && t->sym != S && !(fp->flags&FmtLong)) {
s = t->sym;
if((t == types[t->etype] && t->etype != TUNSAFEPTR) || t == bytetype)
return fmtprint(fp, "%s", s->name); // "byte" または "uint8" を表示
}
テストケースの追加
新しく追加されたtest/alias.go
ファイルは、この機能の正確性を検証するためのテストケースです:
func f(byte) {}
func g(uint8) {}
func main() {
var x int
f(x) // ERROR "byte"
g(x) // ERROR "uint8"
}
このテストは、byte
パラメータを持つ関数にint
を渡した場合のエラーメッセージが「byte」と表示され、uint8
パラメータを持つ関数の場合は「uint8」と表示されることを確認しています。