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

[インデックス 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コンパイラにおけるエラーメッセージの改善を目的として行われました。具体的には、uint8byteの区別をエラーメッセージ内で保持することで、より分かりやすいエラー表示を実現しています。

問題の発生理由

従来のGoコンパイラでは、byteuint8の別名(alias)として定義されていましたが、エラーメッセージ生成時に内部的にuint8に変換されてしまい、ユーザーがソースコードでbyteを使用していてもエラーメッセージではuint8が表示されていました。これは、特に初心者プログラマーにとって混乱の原因となっていました。

改善の必要性

  1. ユーザー体験の向上: 開発者がソースコードでbyteを使用している場合、エラーメッセージでもbyteと表示されるべき
  2. 可読性の向上: エラーメッセージがソースコードの記述と一致することで、デバッグが容易になる
  3. 将来の拡張への準備: コミットメッセージにある「Groundwork for rune」という記述から、将来的なrune型の導入に向けた基盤作り

前提知識の解説

Goの型システムにおけるエイリアス

Go言語では、byteuint8の別名(type alias)として定義されています。現在のGo言語仕様では以下のように定義されています:

type byte = uint8
type rune = int32

コンパイラのアーキテクチャ(2011年時点)

2011年当時のGoコンパイラは、現在とは異なるアーキテクチャを採用していました:

  1. gcコンパイラ: C言語で書かれたコンパイラ(現在はGo言語で書き直されている)
  2. ブートストラップファイル: builtin.c.bootファイルにランタイム関数のインポート情報が記述されていた
  3. 型システム: 内部的に型を表現するためのデータ構造が存在

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.ceqtype()関数に特別な処理が追加されました:

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;
}

この変更により、byteuint8は意味的には等価でありながら、エラーメッセージでは区別されるようになりました。

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

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;

bytetypetyp(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」と表示されることを確認しています。

関連リンク

参考にした情報源リンク