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

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

このコミットは、Go言語の型チェッカーの実験的なステージング版(exp/types/staging)において、Goの組み込み関数(builtins)の型チェックをサポートするための大幅な変更を導入しています。具体的には、append, cap, len, close, complex, delete, imag, real, make, new, panic, print, println, recover, unsafe.Alignof, unsafe.Offsetof, unsafe.Sizeof, assert, traceといった主要な組み込み関数の呼び出しに対する型チェックロジックが実装されています。これにより、コンパイル時にこれらの組み込み関数の誤用を検出し、より堅牢なコードの記述を支援します。

コミット

  • コミットハッシュ: ebf56e086d9d5a357fb904bb5734fa047747a466
  • Author: Robert Griesemer gri@golang.org
  • Date: Tue Sep 25 17:38:43 2012 -0700

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

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

元コミット内容

    exp/types/staging: support for typechecking (most) builtins
    
    This code relies on some functions that are not yet in staging,
    but it get's harder to keep all this in sync in a piece-meal
    fashion.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/6492124

変更の背景

Go言語のコンパイラやツールチェーンにおいて、組み込み関数(builtins)は言語の根幹をなす重要な要素です。これらは通常の関数とは異なり、コンパイラによって特別に扱われます。型チェックの段階でこれらの組み込み関数の呼び出しが正しく行われているかを検証することは、Goプログラムの安全性と正確性を保証するために不可欠です。

このコミットが行われた2012年9月時点では、Goの型チェッカーはまだ発展途上にあり、特に組み込み関数の型チェックは部分的にしかサポートされていなかったか、あるいは未実装の部分が多く存在していました。コミットメッセージにある「This code relies on some functions that are not yet in staging, but it get's harder to keep all this in sync in a piece-meal fashion.」という記述は、型チェッカーの他の部分との整合性を保ちながら、組み込み関数の型チェック機能を段階的に追加していくことの難しさを示唆しています。

このコミットの目的は、主要な組み込み関数の型チェックロジックを一元的にexp/types/stagingパッケージに導入し、型チェッカーの機能を大幅に強化することにありました。これにより、開発者は組み込み関数の誤用による実行時エラーをコンパイル時に検出できるようになり、開発効率とコード品質の向上が期待されました。

前提知識の解説

Go言語の型システム

Go言語は静的型付け言語であり、すべての変数と式には型があります。型システムは、プログラムの安全性と信頼性を確保するために、コンパイル時に型の整合性をチェックします。Goの型は、プリミティブ型(int, string, boolなど)、複合型(array, slice, map, struct, interface, channelなど)、ポインタ型、関数型など多岐にわたります。

組み込み関数 (Built-in Functions)

Go言語には、言語仕様の一部として定義され、特別な意味を持ついくつかの組み込み関数が存在します。これらはimportすることなく直接使用でき、コンパイラによって特別に処理されます。主な組み込み関数には以下のようなものがあります。

  • append: スライスに要素を追加し、新しいスライスを返す。
  • cap: スライス、配列、チャネルの容量を返す。
  • len: スライス、配列、文字列、マップ、チャネルの長さを返す。
  • close: チャネルを閉じる。
  • complex, real, imag: 複素数型を扱う。
  • delete: マップから要素を削除する。
  • make: スライス、マップ、チャネルを初期化して作成する。
  • new: 型のゼロ値を割り当て、そのポインタを返す。
  • panic: ランタイムパニックを引き起こす。
  • print, println: 標準出力に値を出力する(デバッグ用途)。
  • recover: パニックから回復する。
  • unsafe.Alignof, unsafe.Offsetof, unsafe.Sizeof: unsafeパッケージで提供され、型のメモリ配置に関する情報を取得する。

これらの組み込み関数は、それぞれ特定の引数の型と数、戻り値の型が厳密に定められています。型チェッカーは、これらの規則に従って呼び出しがなされているかを検証する必要があります。

型チェッカー (Type Checker)

型チェッカーは、コンパイラのフロントエンドの一部であり、ソースコードが言語の型規則に準拠しているかを検証する役割を担います。具体的には、以下のことを行います。

  1. 型の推論: 変数や式の型を決定します。
  2. 型の整合性チェック: 異なる型の値が互換性のある方法で使用されているかを確認します(例: 整数型と文字列型の加算はエラー)。
  3. 関数呼び出しの検証: 関数の引数の数と型が、関数の定義と一致しているかを確認します。
  4. 組み込み関数の特殊処理: 組み込み関数には特別な規則があるため、それらを適切に処理します。

このコミットは、特に4番目の「組み込み関数の特殊処理」の側面を強化するものです。

技術的詳細

このコミットで追加されたbuiltin関数(src/pkg/exp/types/staging/builtins.go内)は、Goの型チェッカーが組み込み関数の呼び出しを処理する際の中心的なロジックを担っています。この関数は、組み込み関数のID(_Append, _Lenなど)に基づいて、それぞれの組み込み関数に特化した型チェックルールを適用します。

主要な技術的詳細は以下の通りです。

  1. 引数の数と型の検証:

    • 各組み込み関数には、期待される引数の数(bin.nargs)が定義されています。builtin関数はまず、実際の引数の数(len(args))が期待値と一致するかをチェックします。可変長引数(isVariadic)を持つ関数(例: append)は特別に扱われます。
    • 引数の数が不正な場合、check.invalidOpを呼び出してエラーを報告します。
    • 引数の型についても、それぞれの組み込み関数が要求する型(例: appendの第一引数はスライス、closeの引数はチャネル)に合致するかを検証します。
  2. 型推論と結果の決定:

    • 組み込み関数の呼び出し結果の型(x.typ)とモード(x.modevalue, constant, novalue, invalidなど)を決定します。
    • 例えば、lencapは整数型(Typ[Int])を返します。引数が定数の場合は、結果も定数として評価されます。
    • makenewは、引数として与えられた型に基づいて、適切なポインタ型や複合型を返します。
  3. 特殊なケースのハンドリング:

    • append: 第一引数がスライスであること、および追加される要素がスライスの要素型に代入可能であることをチェックします(TODOコメントで代入可能性チェックが残されている)。
    • len / cap: 引数の型(文字列、配列、スライス、チャネル、マップ)に応じて異なるロジックを適用します。配列の場合、引数が定数式であれば結果も定数として評価されます。
    • close: 引数がチャネルであり、かつ送信可能なチャネル(ch.Dir&ast.SEND != 0)であることをチェックします。受信専用チャネルを閉じようとするとエラーになります。
    • imag / real: 引数が複素数型であることをチェックし、結果として対応する浮動小数点型(float32, float64, UntypedFloat)を返します。引数が定数であれば、結果も定数として評価されます。
    • make: 第一引数がスライス、マップ、チャネルのいずれかであることをチェックし、さらに引数の数と型(サイズや容量は整数であること)を検証します。
    • unsafe.Alignof, unsafe.Offsetof, unsafe.Sizeof: これらの関数はunsafeパッケージに属し、コンパイル時に評価される定数値を返します。Alignofは常に1を返し、Offsetofは常に0を返します(TODOコメントでプラットフォーム固有の値の提供が示唆されている)。Sizeofは基本型に対しては定義されたサイズを返します。
    • assert: 自己テストモードでのみ利用可能な組み込み関数で、引数が真のブール定数であることをチェックします。偽であればコンパイル時エラーを発生させます。
    • trace: 自己テストモードでのみ利用可能な組み込み関数で、引数の位置、式、値をダンプします。
  4. エラーハンドリング:

    • 型チェック中にエラーが発生した場合、check.invalidArgcheck.errorfを呼び出してエラーメッセージを生成し、x.modeinvalidに設定してエラー状態を伝播させます。
    • goto Errorステートメントが多用されており、エラー発生時に共通のエラー処理ブロックにジャンプする構造になっています。
  5. 補助関数:

    • implicitDeref: ポインタ型が配列を指している場合に、その配列型を返すヘルパー関数。lencapの処理で利用されます。
    • containsCallsOrReceives: 式が関数呼び出しやチャネル受信を含んでいるかをチェックする関数。lencapの定数評価の可否を判断する際に使用されます。
    • unparen: 式を囲む括弧を再帰的に取り除く関数。unsafe.Offsetofの引数処理で利用されます。

これらのロジックにより、Goの型チェッカーは組み込み関数の呼び出しに対して厳密な検証を行い、開発者がGo言語のセマンティクスに沿ったコードを記述できるよう支援しています。

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

このコミットでは、主に以下の2つのファイルが新規追加されています。

  1. src/pkg/exp/types/staging/builtins.go:

    • このファイルは、Goの組み込み関数の型チェックロジックを実装しています。
    • builtin関数が追加され、append, cap, len, close, complex, delete, imag, real, make, new, panic, print, println, recover, unsafe.Alignof, unsafe.Offsetof, unsafe.Sizeof, assert, traceといった各組み込み関数の型チェックルールがswitch文で分岐して記述されています。
    • また、implicitDeref, containsCallsOrReceives, unparenといった補助関数も定義されています。
  2. src/pkg/exp/types/staging/testdata/builtins.src:

    • このファイルは、builtins.goで実装された組み込み関数の型チェックロジックを検証するためのテストデータ(Goのソースコードスニペット)を含んでいます。
    • 各組み込み関数について、正しい使用例と、意図的に型エラーを引き起こすような不正な使用例が記述されており、期待されるエラーメッセージがコメント(/* ERROR "..." */)として埋め込まれています。これにより、型チェッカーが正しくエラーを検出できるかを確認します。

これらのファイルが追加されたことで、Goの型チェッカーは組み込み関数に対するより包括的で厳密な型チェックを実行できるようになりました。

コアとなるコードの解説

src/pkg/exp/types/staging/builtins.go に追加された builtin 関数がこのコミットの核となります。

func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota int) {
	args := call.Args
	id := bin.id

	// declare before goto's
	var arg0 ast.Expr
	var typ0 Type

	// check argument count
	n := len(args)
	msg := ""
	if n < bin.nargs {
		msg = "not enough"
	} else if !bin.isVariadic && n > bin.nargs {
		msg = "too many"
	}
	if msg != "" {
		check.invalidOp(call.Pos(), msg+"arguments for %s (expected %d, found %d)", call, bin.nargs, n)
		goto Error
	}

	// common case: evaluate first argument if present;
	// if it is an expression, x has the expression value
	if n > 0 {
		arg0 = args[0]
		switch id {
		case _Make, _New:
			// argument must be a type
			typ0 = underlying(check.typ(arg0, false))
			if typ0 == Typ[Invalid] {
				goto Error
			}
		case _Trace:
			// _Trace implementation does the work
		default:
			// argument must be an expression
			check.expr(x, arg0, nil, iota)
			if x.mode == invalid {
				goto Error
			}
			typ0 = underlying(x.typ)
		}
	}

	switch id {
	case _Append:
		// ... appendの型チェックロジック ...
	case _Cap, _Len:
		// ... cap/lenの型チェックロジック ...
	case _Close:
		// ... closeの型チェックロジック ...
	case _Complex:
		// ... complexの型チェックロジック ...
	case _Copy:
		// ... copyの型チェックロジック (未実装部分あり) ...
	case _Delete:
		// ... deleteの型チェックロジック ...
	case _Imag, _Real:
		// ... imag/realの型チェックロジック ...
	case _Make:
		// ... makeの型チェックロジック ...
	case _New:
		// ... newの型チェックロジック ...
	case _Panic, _Print, _Println:
		// ... panic/print/printlnの型チェックロジック ...
	case _Recover:
		// ... recoverの型チェックロジック ...
	case _Alignof:
		// ... unsafe.Alignofの型チェックロジック ...
	case _Offsetof:
		// ... unsafe.Offsetofの型チェックロジック ...
	case _Sizeof:
		// ... unsafe.Sizeofの型チェックロジック ...
	case _Assert:
		// ... assertの型チェックロジック ...
	case _Trace:
		// ... traceの型チェックロジック ...
	default:
		check.invalidAST(call.Pos(), "unknown builtin id %d", id)
		goto Error
	}

	x.expr = call
	return

Error:
	x.mode = invalid
	x.expr = call
}

この関数は、check(型チェッカーのコンテキスト)、x(呼び出し結果を格納するオペランド)、call(AST上の関数呼び出しノード)、bin(組み込み関数のメタデータ)、iotaiota定数の現在の値)を引数に取ります。

  1. 引数の数チェック: まず、len(args)bin.nargs(期待される引数の数)を比較し、引数の過不足がないかを確認します。isVariadicフラグで可変長引数を考慮します。
  2. 第一引数の評価: 多くの組み込み関数は第一引数に特別な意味を持つため、arg0として抽出し、その型(typ0)を評価します。makenewのように第一引数が型である場合と、それ以外で式である場合で処理を分けます。
  3. 組み込み関数ごとの分岐: switch id文を使って、各組み込み関数ID(_Append, _Lenなど)に対応する型チェックロジックに分岐します。
    • caseブロック内では、その組み込み関数に特有の引数の型、数、値の制約を検証します。
    • 例えば、_Appendでは第一引数がスライスであること、_Closeでは引数がチャネルであり送信可能であることなどをチェックします。
    • _Len_Capのように、引数の型によって戻り値の型や定数評価の可否が変わるものについては、さらに内部でswitch typ := implicitDeref(typ0).(type)のような型アサーションを用いて詳細なチェックを行います。
    • _Imag_Realのように、引数が定数であれば戻り値も定数として評価されるロジックも含まれています。
    • unsafeパッケージの関数(_Alignof, _Offsetof, _Sizeof)は、コンパイル時に定数値を返すため、そのロジックが記述されています。
  4. 結果の格納: 型チェックが成功した場合、x.modex.typに呼び出し結果のモードと型を設定します。
  5. エラー処理: 型チェックに失敗した場合、check.invalidOpcheck.invalidArgなどのエラー報告関数を呼び出し、goto Errorで共通のエラー処理ブロックにジャンプします。エラーブロックではx.modeinvalidに設定し、後続の型チェック処理がこの不正なオペランドを適切に扱えるようにします。

このbuiltin関数と、それに付随するimplicitDeref, containsCallsOrReceives, unparenといった補助関数群が連携することで、Goの組み込み関数の複雑な型チェックルールが効率的かつ正確に実装されています。

関連リンク

参考にした情報源リンク

  • Go言語の組み込み関数に関する公式ドキュメント (Go 1.20): https://go.dev/ref/spec#Built-in_functions
  • Go言語の型システムに関する公式ドキュメント: https://go.dev/ref/spec#Types
  • Go言語のコンパイラと型チェックに関する一般的な情報 (Goのソースコード解析に基づく)
  • Web検索: "Go exp/types/staging type checking built-in functions"